C++开发学习笔记2

类和对象

C++面向对象的三大特性:封装、继承、多态。 (与java的特性一致)

封装

struct与class的区别

默认的访问权限不同

  • struct 默认权限为公共
  • class 默认权限为私有

成员属性设置为私有

优点:

  • 将所有的成员属性
  • 对于写权限,我们可以检测数据的有效性

对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设。

构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

一个对象或者变是没有初始状态,对其使用后果是未知

同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

C++利用了构造函数和析构函数决上述问题,这两个函数将会被编译器自动调角,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编泽器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class person{
public:
//构造函数
person(){
std::cout << "无参构造函数-- " << std::endl;
}
//析构函数
~person(){
std::cout << "析构函数-- " << std::endl;
}
};

int main() {
person pe; //在栈上的数据,执行完main 释放这个对象
}

构造函数的分类及调用

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class person {
public:
//无参构造函数
person() {
std::cout << "无参构造函数-- " << std::endl;
}

//有参构造函数
person(int a) {
age = a;
std::cout << "有参构造函数-- " << std::endl;
}

//拷贝构造函数,防止参数的数据被修改,需要加const
person(const person &p) {
age = p.age;
std::cout << "拷贝构造函数-- " << std::endl;
}

//析构函数
~person() {
std::cout << "析构函数-- " << std::endl;
}

private:
int age;
};

void main{
//注意事项:在调用无参构造函数(默认构造函数)时,不要加 () ,因为编译器会认为是一个函数声明,不会认为在创建对象。
person p();

//括号法 (推荐)
person p1;
person p2(10);
person p3(p2);

//显式法
person p1;
person p2 = person(10);
person p3 = person(p2);

person(10); //匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象

//注意事项:不要利用拷贝构造函数初始化匿名对象,编译器会认为:person(p3) = person p3; 对象重定义了
person(p3); //会出错

//隐式转换法
person p4 = 10; //相当于 person p4 = person(10);
}

拷贝构造函数调用的时机

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
//使用一个已经创建完毕的对象初始化吧另一个新对象
person p2(10);
person p3(p2);

>>>>>>>>>>>>>>
void func(person p){
}
//值传递的方式给函数参数传值
person p;
func(p);

>>>>>>>>>>>>>>
//值方式返回局部对象
person func(){
person p1;
return p1;
}

void func2(){
person p = func();
}

int main() {
func2();
}

构造函数调用规则

  • 如果我们写了有参构造函数,编译器不再提供默认构造函数,依然提供拷贝构造
  • 如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数

深拷贝与浅拷贝

浅烤贝:简单的赋值拷贝操作
深烤贝:在堆区重新申请空间,进行拷贝操作

存在的问题解析:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class person {
public:

//无参构造函数
person() {
std::cout << "无参构造函数-- " << std::endl;
}

//有参构造函数
person(int a, int height) {
age = a;
m_height = new int(height);
std::cout << "有参构造函数-- " << std::endl;
}


//析构函数
~person() {
if (m_height != NULL){
//将堆区开辟的数据释放
delete m_height;
m_height =NULL;
}

std::cout << "析构函数-- " << std::endl;
}
int age;
int *m_height;
};

void func2() {
person p1(18,178);
std::cout << "p1 = " << p1.age << std::endl;
std::cout << "p1-2 = " << *p1.m_height << std::endl;

person p2(p1);
std::cout << "p2 = " << p2.age << std::endl;
std::cout << "p2-2 = " << *p2.m_height << std::endl;
}

int main() {
func2();
}

//启动出现错误
//smple_1_1(4069,0x10cfa0dc0) malloc: *** error for object 0x7f8ae6405780: pointer being freed was not allocated
//smple_1_1(4069,0x10cfa0dc0) malloc: *** set a breakpoint in malloc_error_break to debug

错误原因分析:

在执行person p1(18,178);时,p1在栈内存中开辟的一个空间,存放的是age的值,m_height = new int(height);操作是在堆空间开辟了一个空间,并且栈空间的m_height的值就是 new int(height)在堆空间开辟的内存的地址。所以p1开辟的空间存放的是:age=值,m_height=堆空间的地址。

person p2(p1);此操作就发生了浅拷贝,就是把p1字段的值拷贝给p2字段的值,所以p2与p1内存所存储的数据一致。在对象销毁前会走析构函数,所以p2对象销毁的时候会走一次析构函数,在析构函数里会走 delete m_height;释放掉m_height所占的内存,接着在p1对象销毁前也会走一次析构函数,再次去释放了m_height所占的内存,此时就会报错了。

如果避免此错误的发生,就是重写拷贝函数,在拷贝函数里做深拷贝。

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
32
33
34
35
class person {
public:

//无参构造函数
person() {
std::cout << "无参构造函数-- " << std::endl;
}

//有参构造函数
person(int a, int height) {
age = a;
m_height = new int(height);
std::cout << "有参构造函数-- " << std::endl;
}

//拷贝函数
person(const person &p){
age = p.age;
//在堆区重开一个内存
m_height = new int(*p.m_height);
}


//析构函数
~person() {
if (m_height != NULL){
//将堆区开辟的数据释放
delete m_height;
m_height =NULL;
}
std::cout << "析构函数-- " << std::endl;
}
int age;
int *m_height;
};

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

初始化列表

作用:用来初始化属性

语法:构造函数():属性1(值1),属性2(值2),属性3(值3),……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Student {
Student(string name,int age,float score):name(name),age(age),score(score){
}
string name;
int age;
float score;
};


int main() {
Student st("张三", 19, 68.5);
std::cout << " name = " << st.age << std::endl;
std::cout << " age = " << st.name << std::endl;
std::cout << " score = " << st.score << std::endl;
}

类对象作为类成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Soldier{
public:
Soldier(string sName){
m_sName = sName;
}
string m_sName;

};
class General{
public:
//soldier(sName) > Soldier soldier = sName; 隐式转换法
General(string gName,string sName):m_gName(gName),soldier(sName){

}
string m_gName;
//当其他类作为本类的成员,构造的时候会先构造其他类对象,再构造自身对象
Soldier soldier;
};

void ewsrew(){
General g1("李云龙","和尚");
std::cout << g1.m_gName << "的属下是" << g1.soldier.m_sName << std::endl;
}

当其他类作为本类的成员,构造的时候会先构造其他类对象,再构造自身对象。调用析构函数时是先调用自身的析构函数,再调用其他类成员的析构函数

静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称之为静态成员

静态成员分为:

  • 静态成员变量
    • 所有对象共享一份数据
    • 在编译阶段分配内存(全局区)
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

静态成员函数有两种访问方式:

  1. 通过对象进行访问
  2. 通过类名进行访问

(静态成员函数也是如此)

C++对象模型和this指针

成员变量和成员函数分开存储

在C++中,类内和成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上

空对象占用内存空间为:1

C++ 编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址

-------------本文结束感谢您的阅读-------------