类和对象

C语言中结构体中只能定义变量,C++中结构体内不仅可以定义变量,也可以定义函数,C++中更喜欢用class来代替。区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private

定义方式

  1. 声明、定义全放类中。成员函数如果在类中定义,编译器可能会将其当成内联函数处理
  2. 声明放在.h文件中,定义放在.cpp文件中(推荐使用)

类的访问限定符及封装

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用,本质是一种管理

  1. public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问
  2. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

类对象模型

对象中只保存成员变量,成员函数存放在公共的代码段
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐
注意空类的大小,编译器给了空类一个字节来唯一标识这个类

this指针

C++编译器给每个“成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象,函数体中所有成员变量的操作,都是通过该指针去访问。用户不需要来传递,编译器自动完成

  1. this指针类型:类类型* const
  2. this指针是成员函数第一个隐含的指针形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,一般情况由编译器通过ecx寄存器自动传递,不可显示写出,对象中不存储this指针
  3. this指针可以为空,但是不可访问成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class S {
public:
void Print() {
cout << "Print()" << endl;
}
int a;
};

int main() {
S* ptr = nullptr;
ptr->Print(); // 正确,只访问了公共代码段
cout << ptr->a << endl; // 错误,不可访问成员变量
return 0;
}

对象数组

1
2
3
4
5
6
S stuff1[4]; // 调用默认构造
S stuff2[4] = {
S(0,"aaa"),
S(),
S(2,"ccc"),
}; // stuff2[1] stuff2[3] 调用默认构造

最好总是提供一个默认的构造函数。如果没有,则必须确保为数组中的每个对象提供一个初始化项

C++11 列表初始化

C++11扩大了列表初始化的使用范围,使其可用于所有的内置类型和用户自定
义的类型,需要提供与构造函数的参数列表匹配的内容,使用时,可添加等号(=),也可不添加

内置类型的列表初始化

1
2
3
4
5
6
7
8
9
10
11
12
// 内置类型变量
int x{ 1 + 1 };

// 数组
int arr[]{ 1, 2, 3, 4, 5 };

// 动态数组,在C++98中不支持
int* arr = new int[3]{ 1, 2, 3 };

// 标准容器
vector<int> v{ 1, 2, 3, 4, 5 };
map<int, int> m{ {1, 1}, {2, 2,}, {3, 3}, {4, 4} };

自定义类型的列表初始化

1
2
3
4
5
6
7
8
9
10
11
12
S s1{};
S s2; // s1 s2等价

S s3{ 3,"ran" }; // S s3 = { 3, "ran"},可添加等号(=),也可不添加
S s4(3, "ran"); // s3 s4等价

// 直接初始化
S stuff[4]{
S(0,"aaa"),
S(),
S(2,"ccc"),
};

容器类型的列表初始化

实现vector类想要支持列表初始化,需给该类添加一个带有initializer_list类型参数的构造函数,否则Vector<int> v{ 1, 2, 3, 4, 5, 6 };就会报错,编译器以为要创建6个对象: Vector<int> v[6]{ 1, 2, 3, 4, 5, 6 };
改进如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <initializer_list>
template <class T>
class Vector{
public:
// ...
Vector(const std::initializer_list<T>& lst)
: _start(new T[lst.size()])
, _endOfStorage(_start + lst.size())
, _finish(_start) {
for (const auto& e : lst) {
*_finish = e;
++_finish;
}
}

Vector<T>& operator=(std::initializer_list<T> lst) {
delete[] _start;
_start = new T[lst.size()];
_endOfStorage = _start + lst.size();
_finish = _start;
for (auto& e : lst) {
*_finish = e;
++_finish;
}
return *this;
}
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endOfStorage = nullptr;
}

Vector<int> v{ 1, 2, 3, 4, 5, 6 };创建一个对象,并将成员初始化。

initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()

C++11 默认函数控制

显式缺省函数:
C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本
删除默认函数:
在函数声明加上=delete,编译器不生成对应函数的默认版本
与=default不同,=delete必须出现在函数第一次声明的时候

const成员函数

const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
加上const修饰后,隐式的将 类类型* const this 变成 const 类类型* const this

  • 非const对象/成员函数可以调用任何成员函数

  • const对象/成员函数只可以调用const成员函数

mutable

1
2
3
4
5
6
7
8
9
10
11
struct personInfo {
mutable int age=18; // 即使结构变量或类为const,其某个成员也可以被修改
int expr = 2;
};

int main() {
personInfo const p1;
cout << p1.age << endl;
p1.age = 20;
cout << p1.age << endl;
}

static成员

static可以声明成员函数和成员变量

  • 静态成员为所有类对象所共享,不属于某个具体的实例,静态成员变量必须在类外定义,定义时不添加static关键字
  • 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  • 没有隐藏的this指针,不能访问任何非静态成员

友元

类方法和友元只是表达类接口的两种不同机制。但友元会增加耦合度,破坏了封装,所以友元不宜多用

友元函数

  • 友元函数可访问类的私有成员,但不是类的成员函数,不能使用成员运算符来调用
  • 定义在类外部,可以在类定义的任何地方声明,不受类访问限定符限制
  • 当友元函数代码很短时,可以在声明时同时定义,可成为内联函数
  • 一个函数可以是多个类的友元函数
  • 友元函数不能用const修饰

代替友元函数的办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class S {
public:
S& operator+(int n) {
_a += n;
return *this;
}
private:
int _a;
string _s;
};

// 这样就可以避免突破封装了
S& operator+(int n, S& s) {
return s + n;
}

int main() {
S obj;
obj = obj + 1; // obj = obj.operator+(1);
obj = 1 + obj; // 调用S& operator+(int n, S& s)
return 0;
}

友元类

  • 友元类的所有成员函数都可以是另一个类的友元函数
  • 单向,不可传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AAA; // 类的不完全声明
class BBB {
friend class AAA;
public:
BBB() :_b(3) {}
private:
int _b;
};

class AAA {
public:
void Test() {
// 可以访问私有成员
cout << _obj._b << endl;
}
private:
BBB _obj;
};

int main() {
AAA a;
a.Test(); // 3
return 0;
}

内部类

内部类是一个独立的类,它不属于外部类,可以看作外部类的友元类,外部类对内部类没有任何优越的访问权限

  • 可以定义在外部类的任何地方
  • sizeof(外部类)=外部类,和内部类没有任何关系
  • 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AAA {
public:
class aaa {
public:
void Test() {
cout << k << endl; // 3
cout << RED << endl; // 0
cout << GREEN << endl; // 1
}
};
static int k;
enum Color {
RED,
GREEN
};
};

int AAA::k = 3;

int main() {
AAA::aaa a;
a.Test();
return 0;
}