C++:类与对象——第一期

残风也想永存 2024-08-15 16:05:02 阅读 64

一、类与结构体前世孽缘

        在C语言中,我们要描述一本书的是,需要去指出书名,作者,出版日期,价格;而仅仅从数据的单个属性去介绍这个书,是完全不够的。所以我们引入了结构体;结构体中允许,包含多个数据类型,如字符、整型、浮点型、数组、指针等等。而这些数据类型被称为成员变量,又称作成员属性。——【C语言】结构体与位段-CSDN博客

好消息,在C++中,结构体进一步升级成为了类C++兼容C语言中结构体的用法类不但可以包含多个成员变量,而且可以包含成员函数(函数的声明与定义)。成员变量又叫做类的属性,成员函数又叫做类的方法在类中定义与声明合并的函数方法,默认是内联函数。创建对象的时候,只需要类名即可,不用再加上类的关键字声明类的关键字又引入一个:class(类、类型)class与struct声明类的使用,除了默认访问限定符不一样,其余的一致。

二、类的基础概念

1.类的定义格式

类的定义格式:class myclassName {   //类的方法、类的属性  };

2.对象的内存占用

        用类去创建一个对象,系统会在内存中为其开辟空间;那么问题又来了:对象所占用的内存大小该如何计算呢?——对象中主要存储的是成员变量的数据,而成员变量的内存存储遵循内存对齐的原则。

问:对象是如何去访问类的方法的?答:函数在经过编译后会生成一段代码指令,代码指令是无法存储在对象中的,而是存储在代码段中的,而函数的地址就是第一条指令的地址,对象通过该地址去调用函数。问:类的属性存储在对象中毋庸置疑,但问题是:函数地址是存储在对象中的吗?答:错,函数地址也并非存在对象中,也是在代码段中;因为,不同的对象调用函数的时候,都是通过相同的函数地址去调用的,所以将函数地址存储在内存中,会造成内存的浪费。问:若类中没有成员变量,只有成员函数时,对象在占用内存的大小会不会是0呢?答:不会,没有成员变量的对象,占用内存的大小为1;若对象的大小是0,那怎么在内存中标识对象是否存在呢?你说呢?

3.类的访问权限

        讲起访问限定符了,就不得不提一下C++的其中一个特性:封装,在C++中,不太喜欢(也不愿)能在外部,随意访问与修改数据的大小,又或者个人不想让别人使用某个类的方法或访问某个成员数据,与是就引入的访问限定符:public、project、private。

通过访问限定符对成员变量和成员函数进行修饰,可以对其访问权限进行修改public修饰的成员变量与成员函数,可通过对象进行访问;private与project修饰的成员变量与函数,不可通过对象进行访问;访问限定符的作用域在两个访问限定符之间,又或者是类结束——‘}’class声明的类,默认是private修饰struct声明的类,默认是public修饰

4.类域

类形成了一个新域,在类外,定义成员变量与函数的时候,需要通过域操作符(::)去访问。编译器默认查找路径是,当前域,全局域,若找不到,则判定链接错误。定义成员变量,这里定义的成员变量是指的是,定义一个静态的成员变量,而不是非静态的。定义成员函数,是用于函数声明与定义分离的时候,在类外,定义函数的使用,需要告诉编译器我定义的函数是在某个域中的。

<code>class ZMH

{

public:

static void Print();

private:

static int _test;

int _a;

int _b;

};

// 成员变量的定义

int ZMH::_test = 1314;

// 成员函数的定义

void ZMH::Print()

{

cout << ZMH::_test;

}

int main()

{

ZMH::Print();

return 0;

}

三、抽象与实例化

       在现实生活中,建造别墅需要用到设计图纸:里面包含了别墅的大部分信息,以及别墅的整体轮廓。而建筑工人则根据这种设计图纸,可以建造出一套别墅,且可以建造多套别墅。也可以存在多套别墅,允许这多套别墅相同,或有些细节不同。

        而类就好似一张设计图片,创建对象类似于建造一套别墅。类是对象的抽象,对象是类的实例化。所以,声明类的时候,是没有开辟成员变量的空间的,而用类创建对象的时候,才是真正的开辟空间。

四、相关代码+注释解析

        下面是用类封装了一个顺序表,主要是想让大家,可以结合着代码,和代码注释,对类的基本概念,有更深入的理解。

<code>#include<iostream>

#include<assert.h>

using namespace std;

typedef int T; // 本来是个类的模板,因为还未涉及;

class SeqList

{

public:

SeqList(int n = 4)

:_val(new T[n])

, _size(0)

, _capacity(n) {}

~SeqList()

{

delete[] _val;

_size = _capacity = 0;

}

void Chack() //函数直接在类中定义与声明一起,默认内联函数。

{

if (_size >= _capacity)

{

T* tmp = new T[2 * _capacity];

memcpy(tmp, _val, sizeof(T) * _capacity);

delete[] _val;

_val = tmp;

_capacity *= 2;

}

}

void Print() //函数直接在类中定义与声明一起,默认内联函数。

{

for (int i = 0; i < _size; ++i)

cout << _val[i] << " ";

cout << endl;

}

void Insert(T x, int k); // 函数声明

void Remove(int k);

int Find(T x);

void Revise(T x, int k);

private: //私有,类外不能直接访问

T* _val;

int _size;

int _capacity;

};

void SeqList::Insert(T x, int k) //声明与定义分类,如何去定义类域中的函数

{

Chack();

assert(k >= 1 && k <= _size + 1);

int end = _size - 1;

while (end >= k - 1)

{

_val[end + 1] = _val[end];

end--;

}

_val[k - 1] = x;

++_size;

}

void SeqList::Remove(int k)

{

assert(k >= 1 && k <= _size);

int start = k - 1;

while (start + 1 < _size)

{

_val[start] = _val[start + 1];

start++;

}

--_size;

}

int SeqList::Find(T x)

{

for (int i = 0; i < _size; ++i)

{

if (_val[i] == x)

{

return i;

}

}

return -1;

}

void SeqList::Revise(T x, int k)

{

assert(k >= 1 && k <= _size);

_val[k - 1] = x;

}

int main()

{

SeqList s1(4); // 通过声明的类创建对象

s1.Insert(1, 1); // 通过对象,函数方法的调用

s1.Insert(2, 1);

s1.Insert(3, 1);

s1.Insert(4, 1);

s1.Insert(5, 1);

s1.Print();

cout << s1.Find(1) << endl;

s1.Remove(3);

s1.Print();

s1.Revise(1314, 3);

s1.Print();

return 0;

}

五、this指针 

类的方法是存储在代码段中的,不同的类,调用相同的代码指令,那么问题就来了:不同的对象是调用函数的时候,是如何访问不同对象中的数据呢?编译器又是如何知道,我要访问哪个对象的数据呢?这里,就不得不提一下隐式指针的功能了——它是由this关键字起作用,其指向的是,通过谁调用函数的,谁。this指针不可在形参和实参中显示存在,编译器会自己做处理。但是在函数内部,this指针可以显示存在。

int main()

{

SeqList s1(4);

//s1.Insert(&s1,1,1); ->编译器在调用的时候,在第一个形参前面的位置,加上对象的地址,

s1.Insert(1, 1);

s1.Insert(1, 1);

s1.Insert(1, 1);

s1.Insert(1, 1);

SeqList s2;

s2.Insert(2, 1);

s2.Insert(2, 1);

s2.Insert(2, 1);

s2.Insert(2, 1);

//s1.Insert(&s1,1,1); ->编译器会自己做处理,

s1.Print(); // 结果是1 1 1 1

//s1.Insert(&s2,1,1);->在形参和实参位置,不可显示调用,

s2.Print(); // 结果是2 2 2 2

return 0;

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。