类和对象 (上)
类的定义
类定义格式
class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省
略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或
者成员函数。
为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_ 或者 m
开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是
struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。
定义在类⾯的成员函数默认为inline。
类定义的基本格式
C++ 使用class
关键字定义类,末尾必须加分号,结构如下:
class 类名 {// 访问限定符:public/protected/private
public:// 成员函数(行为)void 函数名(参数列表) { /* 函数体 */ }
private:// 成员变量(数据),惯例加前缀_或m_区分数据类型 _变量名;
}; // 分号不可省略
访问限定符
C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限
选择性的将其接⼝提供给外部的⽤⼾使⽤。
public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访
问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有
访问限定符,作⽤域就到 }即类结束。
class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
class Stack
{
public:// 成员函数void Init(int n = 4){array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc申请空间失败");return;}capacity = n;top = 0;}void Push(int x){// ...扩容array[top++] = x;}int Top(){assert(top > 0);return array[top - 1];}void Destroy(){free(array);array = nullptr;top = capacity = 0;}private:// 成员变量int* array;size_t capacity;size_t top;
};struct Person
{
public:void Init(const char* name, int age, int tel){strcpy(_name, name);_age = age;_tel = tel;}void Print(){cout << "姓名:" << _name << endl;cout << "年龄:" << _age << endl;cout << "电话:" << _tel << endl;}private:char _name[10];int _age;int _tel;//...
};struct ListNode
{ListNode* next;int val;
};// 兼容C的用法
typedef struct QueueNode
{struct QueueNode* next;int val;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;int size;
}QU;void QueueInit(QU* q)
{q->head = nullptr;q->tail = nullptr;q->size = 0;
}//类名就是类型
int main()
{Stack st1;st1.Init();st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);cout << st1.Top() << endl;st1.Destroy();Person p1;p1.Init("张三", 18, 1320150);p1.Print();//p1._age++;QU qu;QueueInit(&qu);return 0;
}
类域
类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作
⽤域操作符指明成员属于哪个类域。
类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全
局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知
道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
对象实例化
类本身只是 “设计图”,不占用物理内存;对象是类的实例化结果,会分配实际内存存储成员变量,相当于 “按设计图盖好的房子”。
实例化的本质
一个类可以实例化多个对象,每个对象拥有独立的成员变量(存储各自的数据),但共享成员函数(成员函数编译后存放在代码段,无需每个对象重复存储)。
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:// 声明int _year;int _month;int _day;
};// 定义
int year;int main()
{// 1->N// 类实例化出对象Date d1;Date d2;d1.Init(2024, 8, 6);d2.Init(2025, 8, 7);// d1._year;// d2._year;d1.Print();d2.Print();cout << sizeof(d1) << endl;cout << sizeof(d2) << endl;return 0;
}
对象⼤⼩
分析⼀下类对象中哪些成员呢?类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在运⾏时找,就需要存储函数地址,这个我们以后会讲解。
内存对齐规则
第一个成员变量存于偏移量为 0 的地址。
其他成员变量对齐到 “对齐数” 的整数倍地址(对齐数 = 编译器默认对齐数与成员大小的较小值,VS 默认对齐数为 8)。
对象总大小为 “最大对齐数” 的整数倍。
嵌套结构体时,嵌套结构体对齐到自身最大对齐数的整数倍,整体大小为所有最大对齐数的整数倍。
特殊情况:空类的大小
若类无成员变量(空类),对象大小为1 字节—— 这是编译器为了 “占位”,标识对象的存在(否则无法区分多个空类对象)。
class A
{
public:void Print(){cout << _ch << endl;}private:char _ch;int _i;
};// 没有成员变量的类对象,开1byte,占位,不存储有效数据
// 标识对象的存在
class B
{
public:void Print(){//...}
};class C
{
};int main()
{A a;B b;C c;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;cout << &b << endl;cout << &c << endl;return 0;
}
this指针
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和
Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了
⼀个隐含的this指针解决这⾥的问题编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;
C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。
class Date
{
public:// void Init(Date* const this, int year, int month, int day)void Init(int year, int month, int day){_year = year;_month = month;_day = day;}// void Print(Date* const this)void Print(){// this = nullptr;cout << this->_year << "/" << this->_month << "/" << this->_day << endl;cout << _year << "/" << _month << "/" << _day << endl;}private:// 声明int _year;int _month;int _day;
};int main()
{Date d1;Date d2;// d1.Init(&d1, 2024, 8, 6);// d2.Init(&d2, 2025, 8, 7);d1.Init(2024, 8, 6);d2.Init(2025, 8, 7);// d1.Print(&d1);// d2.Print(&d2);d1.Print();d2.Print();return 0;
}