❀类和对象
# 1. 访问限定符
ob:三大特性:分装、继承、多态
public\protected\private
公有、保护和私有:
保护和私有的区别在继承体现
struct 默认是公有、class默认私有
#include <iostream>
using namespace std;
class A
{
public:
void Print(int x) //成员函数存储在公共代码段 **在类里面实现的函数默认是内联函数**
{
_a = x;
printf("a = %d\n", _a);
cout << this << endl;
}
private:
int _a;
};
int main()
{
A a;
a.Print(10);
printf("%p\n", &a);
return 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
类的大小:
class A{
private:
int _a;
int *p;
};
2
3
4
5
对象中不存成员函数,成员函数存储在公共的代码区。若类没有成员变量,则sizeof(对象) = 1——不存储有效数据,只是为了占位表示对象存在
sizeof(A) = 8
声明和定义
1、声明和定义都放在类中(函数在类中定义),编译器可能会将其当成内联函数处理
变量可以指定类域访问成员变量:A::_a
this : 不允许在实参和形参中显式传递;但是允许在函数中调用,this->(成员变量),一般不会显示写出。this 指向的空间地址不能被改变,即A *const this
,故在成员函数中,this = nullptr 是错误的
this 一般在栈上存储,有的编译器放到寄存器中;空指针不解引用就不会报错
# 2. 默认成员函数-6
# 构造函数——完成对象的初始化
函数名和类名相同,类实例化时自动调用初始化,
不是开空间。无返回值;可以重载。- 默认构造函数(不用传参数的实例对象):1、无参构造函数;2、全缺省构造;3、没写编译器默认产生的 (半缺省和全缺省的不是)
#include <iostream>
using namespace std;
class A
{
public:
void Print() //成员函数存储在公共代码段
{
printf("a = %d\n", _a);
cout << "this:" << this << endl;
}
A(int a = 1) //建议使用(全缺省 = 默认参数),实例化对象时可以不同单独给值;否则实例化对象时必须给参数
:_a(6) // 自己总结的:构造函数对参数的初始化优先级 函数中缺省值(1) > 初始化列表(6) > 声明缺省值(0)
{
_a = a;
cout << "A Constructor a = " << _a << endl;
}
//A() //无参构造函数会和全缺省构成重载,但是调用存在歧义
//{
// cout << "A Constructor Without Parameter" << endl;
//}
private:
int _a = 0;
};
class Person
{
public:
void Print()
{
cout << "In Class Person:" << endl;
cout << _name << " : " << _age << endl;
_a.Print();
}
private:
char* _name[20];
int _age;
//A _a = A(2); //初始化列表没有定义,会优先调用这里的缺省值
//A _a = 3;
A _a; //声明没有给缺省值,则会使用构造函数中的缺省值。
};
int main()
{
A a;
a.Print();
printf("&a = %p\n", &a);
printf("\n");
Person John;
John.Print();
return 0;
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
对于类中变量的初始化:若变量包含自定义类型,则变量会调用自定义类型的默认构造函数(不用参数调用的)初始化,若无 默认构造函数存在则会报错;对于内置类型,不处理。
不允许这样定义定义对象:A a();
与函数定义右异议
# 析构函数
无参数无返回值
class Person
{
public:
Person(size_t namesize = 20)
{
_name = (char*)malloc(sizeof(char) * namesize);
if (_name == nullptr)
{
printf("malloc failed\n");
exit(-1);
}
cout << "Constructor Initial Succeed" << endl;
}
void SendName(char* name = "ZhangSan")
{
int i = 0;
while ((name[i]) != '\0')
{
_name[i] = name[i];
i++;
}
_name[i] = '\0';
}
void Print()
{
cout << _name << endl;
}
~Person()
{
cout << _name << endl;
free(_name);
_name = nullptr;
cout << "Destructor Free Space Succeed" << endl;
}
private:
char* _name;
int _age;
};
int main()
{
Person John;
John.SendName("John");
Person Marry;
Marry.SendName("Marry");
return 0;
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
可以看到实例化对象后,对于生命周期相同的对象,后创建的对象会被先销毁,因为:对象的函数调用会建立栈帧,构造和析构函数符合后进先出。
另外局部变量会被先销毁,static和全局变量后销毁
# 拷贝构造函数
默认的拷贝构造函数:内置成员,完成按照字节序拷贝(值’浅‘拷贝);自定义类型,调用它的拷贝构造。
class B
{
public:
B(int a = 1, int b = 1, int c = 1)
{
_a = a;
_b = b;
_c = c;
}
B(B& t) //拷贝构造函数 与构造函数构成重载 ——只有单个形参,一般用const修饰
//必须使用引用传参,否则会无穷递归
{
_a = t._a * 1;
_b = t._b * 2;
_c = t._c * 3;
}
void Print()
{
cout << _a << "-" << _b << "-" << _c << endl;
}
private:
int _a;
int _b;
int _c;
};
int main()
{
B b1(1,2,3);
B b2(b1); //
b2.Print();
return 0;
}
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
32
33
34
35
# 赋值运算符也是默认成员函数(operator =)
1、内置类型的成员完成字节序值拷贝——浅拷贝
2、自定义类型,调用其operator =
class Date{ //默认生成的赋值浅拷贝可以用
private:
int _month;
int _day;
};
class Stack{ //需要深拷贝,默认的无法满足,否则析构两次
private:
int *_a;
int _size;
}
class MyQueue{ //默认生成的可用,因为他会调用自定义成员的赋值和析构
Stack _pushSt;
Stack _popSt;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:
赋值重载针对两个存在的对象,若对于不存在对象的赋值(初始化将要创建的对象),则调用拷贝构造
A a1;
A a2;
a2 = a1; //赋值重载
A a3 = a1; //拷贝构造
2
3
4
5
- 另外两个:没太大意义
- 取地址和const取地址运算符重载
# 3. 运算符重载
operator不能重载的运算符:
.*
::
sizeof
? :
.
2
3
4
5
优先在类中成员寻找,若无,再到全局寻找重载函数。(VS中)
对于内置类型(int 等)数据,默认支持运算符,但是对于自定义类型,默认不支持,但是可以通过运算符重载使得支持。
class A
{
public:
A(int a1 = 1, int a2 = 1, int a3 = 1)
{
_a1 = a1;
_a2 = a2;
_a3 = a3;
}
bool operator==(A a)
{
return _a1 == a._a1 && _a2 == a._a2 && _a3 == a._a3;
}
private:
int _a1;
int _a2;
int _a3;
};
int main()
{
A a;
A b;
A c(3, 3, 3);
cout << (a == b) << endl; // a.operator==(b)
cout <<(a == c)<< endl;
return 0;
}
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
bool operator>(const A &a){
}
A& operator=(const A &a){ //引用返回,出了作用域对象还在
if(this != &a) // a1 = a1;排除给自己赋值的情况
//.. //如a1 = a2 = a3, a2 = a3返回的是a2的*this(对象别名),a1 = a2中而a是a2的别名。
return *this;
}
A operator++(); //++a a.operator++(&a)
A operator++(int);//后置++,与前置++构成函数重载 a.operator++(&a,0)
2
3
4
5
6
7
8
9
10
11
12
13
14
- 输入输出流
class {
void A::operator<<(ostream &out) //const A* this , ostream &out 第一个参数是左操作数,第二个是右操作数
{
out << _a << endl;
}
}
A a;
a.operator<<(cout); // 调用
//写成全局可以正常调用——但访问不了私有成员,——友元函数
ostream& operator<<(ostream &out, const A& a)
{
out << _a << endl;
return out;
}
istream& operator>>(istream &in, A& a)
{
in >> a._a;
return in;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4. const修饰成员函数
bool operator==(const A a) const // 成员函数后+const保证了this指向的内容不被修改
{
return _a1 == a._a1 && _a2 == a._a2 && _a3 == a._a3;
}
2
3
4
类成员函数默认含有的 是A * cosnt this
,指的是this的值不能被修改,它代表的是传入对象的地址。在外加了const变成了const A* const this
。
关于权限放大和缩小的问题只针对const在*
前的情况。
# 5. 友元
为了能让外部特定的函数访问类中的变量,可以在类中声明这个外部函数——友元函数(可以定义在类中任意位置)
友元不具有传递性、交换性。另外友元是破坏封装的行为,少使用。
class A
{
friend void ExternFuncion(int a); //友元函数
friend class B; //友元类
private:
int _a1;
int _a2;
int _a3;
B b;
};
2
3
4
5
6
7
8
9
10
- 友元函数可以访问成员变量,但不能访问成员函数
- 不能用const修饰,(const修饰*this)
- 可以定义在类中任何地方,不受访问修饰符限制
- 一个函数可以是多个类的友元函数
友元类
- 关系是单项的,B类中可以直接访问A类中的成员变量,而A不能访问B
- 关系不能传递。
# 6. 初始化列表
class A
{
public:
A(int a)
{
_a1 = a;
}
private:
int _a1;
};
class B
{
public:
B(int a = 1, int b = 1, int c = 1)
:_a(a) //变量的定义---相当于在定义的时候初始化 int x = 1;
,_b(b)
,_c(c) //1、const 类型 、引用类型只能通过初始化列表初始化
,_aa(1) //2、自定义类型,且无默认构造函数的必须在初始化列表初始化 ; 其他类型可以在函数体内初始化
{
_a = a;// 允许函数体内和初始化列表共同初始化一个变量,但是初始化列表中一个变量只能出现一次。
_b = b; //相当于先定义后初始化,int x; x = 1;
/*_c = c;*/
}
private:
int _a; //变量的声明
int _b;
const int _c;
A _aa;
};
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
对于自定义类型,虽然在初始化列表没有写,但是仍会在初始化列表中调用其默认构造函数;
建议:尽量使用初始化列表初始化,特别是自定义类型(因为若自定义类型在函数体内初始化,则会调用一次默认构造和一次赋值运算符重载;在初始化列表只会调用一次拷贝构造);应保证声明变量顺序和初始化顺序相同。
注意:成员变量在初始化列表中初始化的顺序依据声明的顺序,如上,无论初始化列表顺序怎么变,则初始化始终按照_a\ -b \ _c\ _aa顺序初始化。
# 7. 其他
# 初始化自定义类变量——隐式类型转换
无关类型——强制转换
相似类型——隐式转换
double d = 1.1;
int &i = d; //错误 应为const int &i = d; 隐式类型转换先赋值给临时变量
2
class A
{
public:
//explicit A(int a) //explicity 关键字可以保证A类型变量不能被隐士转换
A(int a)
{
_a1 = a;
cout << "Construction" << endl;
}
A(const A& a)
{
_a1 = a._a1;
cout << "Copy Construction" << endl;
}
~A()
{
cout << "Destructor :" << _a1 << endl;
}
private:
int _a1;
};
int main()
{
A a1(1);
A a2 = 2; // int -> A类型;隐式转换 先构造临时对象 A(2),再用这个对象拷贝构造a2,编译器优化后变成一个构造
A(3); //匿名对象的生命周期只在本行
A a4 = 4;
return 0;
}
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
32
33
# 静态成员变量
class A
{
public:
static int Get_a1() //静态成员函数,没有this指针,且不能访问非静态成员,只能访问静态成员变量和函数
{
return _a1;
}
private:
static int _a1; //_a1存放在静态区,不计入类的大小,属于整个类(可用于计算该类定义了多少对象)
};
int A::_a1 = 0; //静态成员变量的初始化,属于该类但不属于某个对象
int main()
{
A a1;
cout << sizeof(a1) << endl;
cout << sizeof(A) << endl;
a1.Get_a1; //对静态成员函数,使用对象或指定类域 -突破类域都可以调用。
A::Get_a1
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 类变量缺省值
class B
{
public:
B()
{
cout << "B Constructor" << endl;
}
private:
int _b;
};
class A
{
public:
A()
: _a2(1)
{
cout << "A Constructor" << endl;
cout << "_a2 = " << _a2 << endl;
_p[0] = 'h';
_p[1] = 'a';
_p[8] = '\0';
cout << "_p = " << _p << endl;
}
private:
static int _a1; //静态成员*不能*给缺省值
int _a2 = 0; //声明给缺省值,这里仍然是对对象的声明。初始化列表若没有自己写对成员变量初始化,则使用缺省值初始化
int* _p = (int*)malloc(10*sizeof(int)); //现在不会开辟新的空间,
//但是调用构造函数若初始化列表中没有,则会调用这里的语句
B b1 = 10;
};
int A::_a1 = 0;
int main()
{
A a1;
return 0;
}
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
32
33
34
35
36
37
38
39
40
41
# 内部类
类中定义一个类
内部类和在全局定义类基本一样,只受到外部类的限制
内部类天生是外部类的友元。(单向的)
# c++动态内存分配
class A
{
public:
A(int a = 0)
: _a2(a)
{
cout << "A Constructor" << endl;
}
private:
//static int _a1;
int _a2 = 0;
};
int main()
{
//int* p1 = (int*)malloc(0xffffffff);//malloc开辟出4GB的空间——64位,不会初始化
//cout << sizeof(p1) << endl;
//free(p1);
int *p1 = (int*)operator new(sizeof(int)* 4); //库函数operator new
operator delete(p1);
A* pa = new A[10]; // new 开空间会调用类的构造函数进行初始化 C++11支持初始化: A[10] = {1,2,3},其他默认为0
cout << sizeof(*pa) << endl;
delete[] pa; // delete会调用类的析构函数并释放空间
int* pnit1 = new int[10]{1,2,3}; //对于内置类型的指针,定义及初始化
int* pnit2 = new int(1); //内置类型的指针单个空间初始化
cout << " pnit1[3]:" << pnit1[3] << endl;
cout << "*pnit2:" << *pnit2 << endl;
delete[] pnit1;
delete pnit2;
return 0;
}
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
32
33
34
- new(操作符)
实际上调用operate new函数 + 类对象构造函数
进行空间的分配,而operate new的底层调用malloc和内存分配失败异常判断
。
A* pa = (A*)operate new(sizeof(A));
new(pa)A(2); //placement-new,调用类中构造函数初始化
2
以上实现等价与new 注意:构造函数不能显式调用,显示必须使用定位new。
- delete
调用析构 + operate delete
,operate delete 调用 free + 失败异常机制
paa->~A();
operator delete(paa);
2
以上实现等价与new 注意:析构函数可以如上显式调用
operate delete和operate new可以在类中定义重载函数
void* operate new(size_t n)
{
...
}
void operate delete(void* p)
{
...
}
2
3
4
5
6
7
8
- 自定义对象用malloc开辟空间并初始化 定位new
A* pa = (A*)malloc(sizeof(A));
new(pa)A(2); //placement-new,调用类中构造函数初始化malloc空间
2
- 面向对象
面向对象出现错误:抛异常
面向过程出现错误:打印错误码errno
try
{
char *p = new char[1024u*1024u*1024u*2u - 1];//创建失败不会异常终止
}
catch (const exception& e) //try 正常执行,跳过
{
cout << e.what() << endl;
}
2
3
4
5
6
7
8
new/delete回调用构造和析构函数
new失败后抛异常。
replacement new
定位new——对已有的空间调用构造函数初始化
int mian()
{
A*p = (A*)malloc(sizeof(A));
new(p)A;
new(p)A(1);
A* p1 = new A(1);
delete p1;
// 等价于
A* p1 = (A*)operator new(sizeof(A));
new(p1)A(1);
p1->~A(); //析构函数可以显式调用,构造不行
operator delete(p1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 内存泄漏
主动防范:智能指针
# 编译器的优化
只有构造和拷贝构造才会被优化,在一个
表达式的连续步骤中,临时对象和匿名对象(通常在传值参数或函数返回值)会被优化掉。
匿名对象的生命周期只在这一行,结束后会调用析构函数。
# 权限的放大与缩小
#include <iostream>
using namespace std;
class A{
public:
A(int a = 1)
:_a(a)
{
cout << "constructor" << endl;
}
void Print() const //A* const this —— 原生的this形参的表示,this的内容不能被更改
{ //const A* const this —— 加了const后的,*this(对象)不会被改变,即this指向的内 容不会被改
cout << "hello world" << endl;
}
private:
int _a;
};
int main()
{
A a1;
a1.Print(); // &a1 -> A*
const A a2;
a2.Print(); //掉用不动,&a2 -> const A*
return 0;
}
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
# 其他
- 类的定义存在代码段。成员函数在代码段。对象中不存放普通函数的地址,只存放成员变量。