类与对象
面向对象三大特性:封装、继承、多态
public
:可以在类外访问
private/protected
:只能在类内访问(友元除外)
struct 与 class
C++中,struct与class都可以定义一个类,主要区别是:
- class 默认成员变量特性为public
- struct 默认成员变量特性为private
类的作用域
C++中{}
括起来的就是一个作用域
声明一个类,也就声明了一个类域,类的所有成员都在类的作用域中**。**在类体外定义成员,需要使用 ::
作用域解析符指明成员属于哪个类域。
编译器链接过程在寻找函数的时候,先在类域中寻找,再寻找全局的。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout<<_name<<" "_gender<<" "<<_age<<endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化
通过一个类实例化出来的对象。其成员变量会单独存储,但是类的成员函数公共存储,以节省空间。
一个空类的大小为1字节,因为空类也可以去实例化,也要去为这个变量开辟空间
sizeof(类名)
计算的这个类实例化对象的大小
类的存储也遵循内存对齐规则
this 指针
成员函数默认有一个this
指针,再调用该成员函数的时候会自动将对象的地址进行传参,参数名为this
Date* const this // 是一个常指针,即指向的位置不可以更改
- 若不通过
this
指针访问成员变量的话,则根据就近原则访问变量 this
指针在代码中是隐藏的,编译器不允许删除- 调用成员函数时,不能显示传实参给
this
- 定义成员函数时,不能显示声明形参
this
- 可以直接显示使用
this
this
作为成员函数的第一个变量
this指针存放在哪里?
- 通常情况下存放到栈中(随着函数栈帧开辟一齐压栈)
- 有时编译器为了优化,将
this
存放到寄存器中
类的6个默认成员函数
这六个默认成员函数,如果有显示声明则编译器就不会提供。
内置类型与自定义类型
C++将变量类型分为两类:内置类型(基本类型)、自定义类型
**内置类型:**int、double、char、指针… 编译器自带的变量类型
**自定义类型:**类(class),结构体(struct)用户自定义的变量类型
构造函数
构造函数是一个特殊的成员函数,无返回值,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
用于初始化创建的对象
默认构造函数
无需参数就可以调用的构造函数就是默认构造函数,并且默认构造函数只能有一个:
可以是:无参数、全缺省参数、用户未提供编译器默认生成的构造函数
默认生成的构造函数
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义构造函数编译器将不再生成。
编译器默认生成的构造函数并不是一个空函数:
在默认生成构造函数中,内置类型不会进行处理,自定义类型则会去调用他自己的默认构造函数,此时,若该自定义类型没有定义默认构造函数就会报错。
析构函数
对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。与构造函数一样,无返回值,无参数。
默认析构函数
对于内置类型的成员变量不做处理,对于自定义类型会去调用它的析构函数。
拷贝构造函数
自定义类型用一个已存在的同类型变量去初始化一个对象,就是拷贝构造函数
形参必须是引用,否则会引发无穷递归
传参或传返回值过程中,若存在连续的调用构造,拷贝构造函数编译器会进行优化
默认拷贝构造函数
若用户未显示定义拷贝构造函数则编译器会默认提供
- 对于内置类型会直接进行值拷贝 简单的字节序拷贝**(浅拷贝)**
- 对于自定义类型会去调用它的拷贝构造函数
浅拷贝会导致指针指向的空间被析构两次程序崩溃并且指针指向的空间不能被复制过去
这里涉及到了浅拷贝与深拷贝的问题
运算符重载
函数名字为:关键字 operator + 需要重载的运算符符号。
函数原型:返回值类型 + operator操作符 + (参数列表)
// > 运算符重载
bool operator>(const Date& d) const;
注意:
- 只能重载已有的运算符符号。
.*
::
sizeof
? :
.
这五个运算符不能重载- 运算符重载不能改变运算符的含义 也就是要与运算符对于内置类型变量操作的含义相同。
- 作为类成员的重载函数时,其形参看起来比操作数数目少一个参数,是因为对于成员函数的操作符有一个默认的形参this,限定为第一个形参
赋值重载
两个已经存在的对象之间进行赋值拷贝
默认赋值重载 浅拷贝
- 内置类型直接字节序拷贝
- 自定义类型调用其赋值重载
取地址运算符的重载
C++有默认生成的&取地址符的重载,就是取这个实例化出的普通对象和const对象的地址
++运算符重载
为了区分前置和后置++,官方规定增加一个参数占位int
有占位参数的是后置++
匿名对象 生命周期只在这一行代码中
Date(2022); // 这就是一个匿名对象
const对象
注意传递this指针的时候权限放大的问题
成员函数的参数列表的后面加上const,相当于给this指针加上了一个常属性
const Date* const this;
友元
友元分为友元类,友元函数
友元提供了便利,但是破坏了类的封装性
友元是单向的,B
中声明了A是友元,则A
可以访问B中私有和保护的成员,但不意味着B
就可以访问A
中的私有或保护成员。
class B
{
public:
B(int b):_b(b)
{}
private:
int _b;
};
void fun()
{}
class A
{
friend void fun(); //友元函数的声明
friend B; // 友元类的声明
public:
A(int a)
{
_a = a;
cout << "A(int a)" << endl;
}
private:
int _a;
};
初始化列表
为成员变量提供了一个可以初始化的地方
对于const成员变量,其在进入构造函数之前就已经被定义了,因此要在初始化列表中对const成员进行初始化
初始化列表应注意的地方
- const成员,引用成员,没有默认构造函数的成员,这三种成员变量必须在初始化列表处进行初始化,否则就会被编译器赋为垃圾值
- 对于其他类型成员变量,如int、double等,在哪里初始化都可以
- 对于非const的自定义类型,若没有提供初始化列表,编译器会默认在初始化列表处通过调用其默认构造函数进行初始化
- 成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的位置无关
class B
{
public:
B(int b):_b(b) // 初始化列表
{}
private:
const int _b;
};
static 静态成员
- 静态成员变量属于整个类,生命周期在整个程序运行期间
- 静态成员函数没有this指针,只能访问静态的成员变量和成员函数
- 静态成员和静态成员函数使用类和对象均可以访问
- 静态成员只能在类外全局位置初始化
class B
{
private:
static int static_B;
};
int B::static_B = 10; // 类外全局位置初始化
缺省值
首先,C++不允许在类的声明处对成员变量初始化
但对于缺省值而言,如果在初始化列表中,没有对成员变量初始化,就会使用缺省值
class B
{
public:
B(int b):_b(b)
{}
private:
int _b = 10; // 缺省值为10
};
内部类
内部类和在全局定义基本是一样的,只是需要受外部类的限制
内部类天生就是外部类的友元,也就是内部类天生就可以访问外部类的私有和保护
PS: 这个不常用