1. 类的定义
1.1 类的定义格式
- class是定义类的关键字,由关键字 + 类名 + 类体组成,注意在类定义结束后还有一个分号不要忘记。
class Date {
public:
Date(int year, int month, int day){...}
private:
int _year;
int _month;
int _day;
};
比如这个日期类,Date是类名,{}中是类体;类中private部分的是成员变量,public下的Date函数是成员函数(构造函数)。
在C++中struct也可以定义类,与C相比,C++中的struct被升级为了类,所以可以在里面定义函数,不过一般还是选用class定义类
1.2 访问限定符
类的实现,实际上是封装思想的体现,所以通过访问限定符给予类能被访问的不同权限
-
public修饰的成员在类外可以被访问,protected和private修饰的成员在类外不能直接进行访问(在继承中能更直观看见区别)。
-
访问限定符的作用域从当前限定符到下一个限定符出现为止,若没有下一个,则持续到类体结束。
-
class中定义成员时,没有写限定符默认为private,struct默认为public。 ⼀般成员变量都会被限制为private/protected(防止被恶意修改),需要给别人使用的成员函数放在public。
1.3 类域
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时,要用::作用域操作符指明成员属于哪个类域。
!!!声明和定义分离,需要指定类域!!!
2. 实例化
2.1 实例化概念
- ⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。
- 类是对象进行的一种抽象描述,只是限定了类有哪些成员变量,不过在这里只是声明,没有分配实际空间,只有用类实例化对象时,才会分配空间。
还是这个例子,private中成员变量仅仅是声明。
int main(){
//Date类实例化出对象d1,d2
Date d1;
Date d2;
......
}
只有在这里,才是真正意义上在物理内存中为对象分配了空间。
2.2 对象大小
类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令 存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。d1,d2有各自独立的成员变量用来存储数据,但是d1,d2的成员函数的指针都是一样的,存储在对象中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外啰嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指 令[call 地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在 运⾏时找,就需要存储函数地址。
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
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;
return 0;
}
在上面代码中,对象A大小是8字节(char _ch 和 int _i)这里只有四个字节,由于存在内存对齐,所以_ch(1字节) +3字节(内存对齐) = 4字节;
对象B中无成员变量,但有成员函数,由于成员函数不算入对象空间(存放在公共的代码段),所有对象共享,为了区分不同的空类对象(确保它们在内存中有唯一的地址),编译器会插入 1 字节占位符,所以 sizeof(b)为 1;
对于对象C,同理,编译器同样会分配 1 字节占位符,保证对象 c在实例化时有合法的内存地址,输出为 1。
内存规则:对象大小 = 所有非静态成员变量大小之和 + 内存对齐填充 +(空类/无成员时的 1 字节占位)。
3.this指针
Date类中存在多个成员函数时(如Init函数和print函数),函数体中没有关于不同对象的区分,对象在调用这些成员函数时,成员函数为了区分到底是哪一个对象,所以C++这里给了一个隐含的this指针解决这里的问题。
比如vector中;
void push_back(const T& value)
在编译器底层展开是
void push_back(vector<T, Allocator> * this,const T& value)
这里就有一个隐含this指针,指向调用该函数的具体 vector对象。
C++规定不能在实参和形参的位置显式的写this指针(编译时编译器会处理),但是可以在函数体内显式使⽤this指针。
#include using namespace std;
class A {
public:
void Print() {
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
这段代码的编译运行结果是正常的,虽然p指针是空的,但是并没有解引用这个指针(没访问成员变量),空指针调用成员函数不一定报错,取决于函数体内是否访问了成员变量。
#include using namespace std;
class A {
public:
void Print() {
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
这里成员函数中加入了要打印_a这个成员变量,我们知道,想要在成员函数中调用成员变量需要一个隐含的this指针来帮忙,在这里p相当于这个this指针,存在这个关系:
*p(*this) = _a
由于main中将p初始化为了nullptr,所以这里会导致空指针解引用,会程序崩溃!
最后可能在面试中会问到:
this指针存在于内存的哪个区域?
A.栈 B.堆 C.静态区 D. 常量区 E.对象里面
这里显而易见地排除E,this指向对象,不可能在对象中,实际上,在main函数调用Print()时,会建立一个新的函数栈帧,main将对象地址(this)压入栈,从而跳转到Print去执行,在 Print函数执行期间,this形参就存在于 Print的栈帧中。函数执行完毕,栈帧销毁,this自动释放。这完美符合“栈”的先进后出和局部生命周期特性。