1.C和CPP的差异

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
#include <iostream> // C++标准支持  C++的与众不同

using namespace std; // 命名空间 C++ 的特性 (Java语言的内部类)

int main() {

// C++里面可以运行C语言,可以调用C语言,反之 就不行C语言无法运行C++
printf("降龙十八掌(C版)\n");

// std::cout << "C++语言的学习" << std::endl;
cout << "C++语言的学习" << endl; // 因为你前面引入了命名空间,省略std::

// endl == \n 都是换行的含义一样

// Kotlin也有操作符重载, Kotlin就是各个语言的精华所在

// << 不是属性里面的运算,操作符重载,后面会讲
cout << "擒龙功" << endl;

cout << "铁头功\n"
<< "金刚腿\n"
<< "铁布衫\n";

return 0;
}

2.C与C++常量

C语言的常量,其实是个假常量。

1
2
3
4
5
6
7
8
int main() {
const int number = 100;
// number = 200;
int * numP = &number;
*numP = 10000; // 可以修改
printf("%d\n", number);
return 0;
}
1
2
3
4
5
6
7
8
9
int main() {
const int number = 100;
// number = 200;
// 我的编译器,编译不通过, 有的编译器,编译通过,但是运行报错(结论:就不能改)
// int * numP = &number;
// *numP = 10000;
printf("%d\n", number);
return 0;
}

3. &引用

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
51
52
53
54
55
56
57
58
#include <iostream>

using namespace std;

// 指针取地址 互换 C语言第一节课的内容
// 接收number1/number2的地址,取改地址的值,来完成的互换
void numberChange(int * number1, int * number2) {
int temp = 0;
temp = *number1;
*number1 = *number2;
*number2 = temp;
}

// C++提倡的引用
void numberChange2(int & number1, int & number2) {

// 如果不采用引用,main numberChange2 内存地址是不一样的
// 如果采用引用,main numberChange2 内存地址是一样的,为什么呢?
cout << "numberChange2 " << "n1地址:" << &number1 << " , n2地址:" << &number2 <<endl;

int temp = 0;
temp = number1;
number1 = number2;
number2 = temp;
}

int main() {

int number1 = 10;
int number2 = 20;

cout << "main " << "n1地址:" << &number1 << " , n2地址:" << &number2 <<endl;

// numberChange(&number1, &number2);
numberChange2(number1, number2);

cout << "n1:" << number1 << " , n2:" << number2 << endl;

cout << endl;

// 引用做实验,来学原理:

// 第一部分,不采用 &
/*int n1 = 999;
int n2 = n1;
cout << &n1 << "---" << &n2 << endl;*/ // 0xffffcbb4---0xffffcbb0

// 第二部分,采用&
int n1 = 999;
int & n2 = n1;
int & n9 = n1;
n2 = 777;
n9 = 9527;
cout << "地址:" << &n1 << "---" << &n2 << endl;
cout << "值:" << n1 << "---" << n2 << endl;

return 0;
}
1
2
3
4
5
6
7
运行结果
main n1地址:0x61fe0c , n2地址:0x61fe08
numberChange2 n1地址:0x61fe0c , n2地址:0x61fe08
n1:20 , n2:10

地址:0x61fe04---0x61fe04
值:9527---9527

4.常量引用

C++的常量是真的常量,C是伪常量,C的常量可以通过指针修改。

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
#include <iostream>
#include <string.h>

using namespace std;

// 代码的统一性
typedef struct {
char name[20];
int age;
}Student;

// 常量引用:Student不准你改 == const Student &
// 插入数据库,Student的信息给插入数据库
void insertStudent(const Student & student) {
// 内鬼 卧底
// strcpy(student.name, "李元霸"); 不能这样修改

Student student2 = {"刘奋", 43};
// student = student2; 不能这样修改

// 只读的了,可以安心插入数据库了
cout << student.name << "," << student.age << endl;
}

int main() {
// 用户提交的Student数据
Student student = {"张无忌", 30};
insertStudent(student);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方法内部可以修改student,因此会报问题,需要使用到常量引用,在方法体内部不能修改
void insertStudent(Student student){

}

// 正确写法。
// 拷贝构造函数
void insertStudent(const Student &student) {
//方法内对student为只读,不可修改
// student是旧地址
// this是新地址
// student2就是新地址了
student2 = this;
}

5.C++中栈空间和堆空间

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
#include "Student.h"

int main() {
// 规范写法:要有 头文件.h .hpp -- 实现文件 .c cpp
std::cout << 1 << std::endl;

// TODO ======= 下面是栈空间
Student student1; // 栈区开辟空间的
// 赋值
student1.setAge(99);
student1.setName("李连杰");
cout << "name:" << student1.getName() << " ,age:" << student1.getAge() << endl;

// TODO ======= 下面是堆空间
Student * student2 = new Student(); // new/delete
// 赋值
student2->setAge(88);
student2->setName("李元霸");
cout << "name:" << student2->getName() << " ,age:" << student2->getAge() << endl;

if (student2)
delete student2; // 必须手动释放堆空间的对象student2
student2 = NULL; // 指向NULL的地址区域
// free(student2); // 不能这样写,不规范,会被鄙视的

return 0;

// 正规的流程:【xxx.so(C/Cpp的实现代码) 用户拿到xxx.so】, 头文件
} // main函数弹栈后,会释放栈成员 student1

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 自定义命名空间
namespace jack1 {
int age = 33;
char * name = "jack猛男1";

void show() {
cout << "name:" << name << ", age:" << age << endl;
}

void action() {
cout << "jack1 action" << endl;
}
}

// TODO ------ 命名空间里面重复的函数
// 自定义命名空间
namespace jack2 {
void action() {
cout << "jack2 action" << endl;
}
}

// TODO ------ 小概率会遇到的情况,命名空间的嵌套
// 自定义命名空间
namespace jack3 {
namespace jack3Inner {
namespace jack3Inner1 {
namespace jack3Inner2 {
namespace jack3Inner3 {
void out() {
cout << "爱恨情仇人消瘦,悲欢起落人寂寞" << endl;
}
}
}
}
}
}

// 声明各个写的 命名空间
// using namespace jack1;

int main() {
cout << "命名空间" << endl;

// 声明各个写的 命名空间
using namespace jack1;

int ageValue = jack1::age; // 方式1 使用 刚刚声明的命名空间
jack1::show(); // 使用 刚刚声明的命名空间

ageValue = age; // 方式2 直接去引出来 ::
show(); // 直接去引出来 ::


// TODO ------ 命名空间里面重复的函数
using namespace jack2;
// action(); 很尴尬
jack1::action();
jack2::action();

// TODO ------ 小概率会遇到的情况,命名空间的嵌套

// 第一种方式 先声明命名空间 再使用
using namespace jack3::jack3Inner::jack3Inner1::jack3Inner2::jack3Inner3;
// 再使用
out();

// 第二种方式 直接使用
jack3::jack3Inner::jack3Inner1::jack3Inner2::jack3Inner3::out();

return 0;
}

7.构造与析构函数

// new/delete 是一套 会调用构造函数 与 析构函数 【C++标准规范】

// malloc/free是一套 不调用构造函数 与 析构函数 【C的范畴,虽然不推荐,但是也是可以的】

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <iostream>
#include <string.h>
using namespace std;

class Student {

// 构造函数
public:
// 空参数构造函数
Student() {
cout << "空参数构造函数" << endl;
}

// 一个参数的构造函数
// :Student(name, 87) 等价 1.调用两个参数的构造函数, 2.再调用当前函数
// 学生说的:先调用两个参数的,再调用一个的
Student(char *name) :Student(name, 87) {
cout << "一个参数的构造函数" << endl;
this->name = name;
}
// 系统源码中是写的
// :name(name) 等价 this->name = name;
/*Student(char * name) :name(name) {
cout << "一个参数的构造函数" << endl;
}*/

// 两个参数的构造函数
Student(char *name, int age) {
// this->name = name;

// 堆区
this->name = (char *) (malloc(sizeof(char *) * 10));
strcpy(this->name, name);

this->age = age;
cout << "两个参数的构造函数" << endl;
}

// 析构函数 Student对象的,临终遗言,Student对象被回收了,你做一些释放工作
// delete stu 的时候,我们的析构函数一定执行
// free不会执行析构函数,也意味着,你没法在析构函数里面,做释放工作, malloc也不会调用构造函数
~Student() {
cout << "析构函数" << endl;

// 必须释放 堆区开辟的成员
if (this->name) {
free(this->name);
this->name = NULL; // 执行NULL的地址,避免出现悬空指针
}
}

// 私有属性
private:
char *name;
int age;

// 公开的 set get 函数
public:
int getAge() {
return this->age;
}

char *getName() {
return this->name;
}

void setAge(int age) {
this->age = age;
}

void setName(char *name) {
this->name = name;
}
};

int main() {
// TODO =========== 下面是栈区 开辟空间的

/*Student stu; // 调用 空参数构造函数
stu.setAge(34);
stu.setName("李元霸");
cout << "name:" << stu.getName() << ", age:" << stu.getAge() << endl;*/

// Student stu("雄霸", 30);
/*Student stu("李连杰");
cout << "name:" << stu.getName() << ", age:" << stu.getAge() << endl;*/

// TODO =========== 下面是堆区 开辟空间的 堆区必须手动释放,否则内存占用越来

// 系统源码中,会看到,很多使用 new 关键字

// *stu ->:调用一级指针的成员
// new/delete
// C++中,必须使用 new/delete 一套
Student *stu = new Student("杜子腾", 26);
cout << "name:" << stu->getName() << ", age:" << stu->getAge() << endl;
delete stu;

// free(stu); 这样写是不规范的,不按人家规则来

// malloc 你的构造函数都没有调用,这个不行的
/*Student *stu2 = (Student*) malloc(sizeof(Student));
free(stu2);*/

// 纠结:C++有析构函数(临终遗言 释放工作) Java KT 临终遗言 是什么?

return 0;
}

8.拷贝构造函数

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <iostream>
#include <string.h>

using namespace std;

class Student {

public:
Student() { cout << "空参数构造函数" << endl; }

// 两个参数的构造函数
Student(char *name, int age) : name(name), age(age) {
cout << "两个参数构造函数" << endl;
}

// 析构函数
// ~Student(char * name) { } 这样写,就不是析构函数了,如果你这样写,C/C++编译器对你很无语
~Student() {
cout << "析构函数" << endl;
}

// 以前是默认有一个拷贝构造函数,stu2 = stu1 默认赋值 【隐士 你看不到】

// 拷贝构造函数,它默认有,我们看不到,一旦我们写拷贝构造函数,会覆盖她
// 对象1 = 对象2
// 覆盖拷贝构造函数
Student(const Student & student) { // 常量引用:只读的,不让你修改
cout << "拷贝构造函数" << endl;

// 我们自己赋值
// 为什么要自己赋值,自己来控制,就可以 例如:-10
this->name = student.name;
this->age = student.age - 10;

cout << "自定义拷贝构造函数 内存地址 " << &student << endl;
}

// 私有属性
private:
char *name;
int age;

// 公开的 set get 函数
public:
int getAge() {
return this->age;
}

char *getName() {
return this->name;
}

void setAge(int age) {
this->age = age;
}

void setName(char *name) {
this->name = name;
}
};

struct Person {
int age;
char *name;
};

// TODO = 号的意义 隐士代码,引出 拷贝构造函数
/*
int main() {
Person person1 = {100, "张三丰"};

// = 你看起来,没有什么特殊,隐士的代码:你看不到 C/C++编译器 会把p1的成员值赋值给p2成员
Person person2 = person1;

// 地址是不同的,说明是两个对象,person2拷贝了persion1的内容
// 0x61fe10
// 0x61fe00
// 张三丰, 100
cout << &person1 << endl;
cout << &person2 << endl;

cout << person2.name << ", " << person2.age << endl;

// 思考:对象 对象1=对象2 默认的 拷贝构造函数

return 0;
}*/

// TODO 拷贝构造函数
/*
int main() {
Student stu1("李鬼", 34);
Student stu2 = stu1;

cout << stu2.getName() << " , " << stu2.getAge() << endl;
cout << "main " << &stu1 << endl; // 地址的打印是一样的, 注意:cnetos的环境 地址打印有差异,要注意



// TODO 拷贝构造函数的注意点:
// Student stu1("李鬼", 34);
//
// Student stu2;
// stu2 = stu1; // 这样赋值是不会调用 自定义拷贝构造函数,但是会调用默认赋值
// Student stu2 = stu1; // 这样赋值是会调用 自定义拷贝构造函数,我们自己赋值
//
// cout << stu2.getName() << " , " << stu2.getAge() << endl;

getchar(); // 程序等待在这一行

return 0;
} // main函数弹,stu1栈成员会回收 stu2栈成员会回收
*/

// TODO 这种写法 拷贝构造函数 到底会不会调用
int main() {
Student *student1 = new Student("杜子腾", 39);

Student *student2 = student1; // 压根就不会执行拷贝构造函数(指针指向问题,和我们刚刚那个 对象2=对象1 是两回事)

// 原理,为什么不会? 纠结

student2->setAge(99);

cout << student1->getName() << student1->getAge() << endl;

// “->”调用一级指针的成员 和 “.”非指针的操作 有啥区别

return 0;
}

9.指针常量、常量指针和常量指针常量

命名方面规律:命名顺着读,const靠近谁,谁就不能修改。

  • const靠近*叫指针常量,int * const p;允许修改值,不允许修改地址;
  • const靠近int叫常量指针,const int * p; 允许修改地址,不允许修改值。
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
#include <iostream>
#include <string.h>
#include <string.h>

using namespace std;

int main() {

// *strcpy (char *__restrict, const char *__restrict);
// strcpy()


int number = 9;
int number2 = 8;

// 大道至简 一分钟搞定

// 常量指针
const int * numberP1 = &number;
// *numberP1 = 100; // 报错,不允许去修改【常量指针】存放地址所对应的值
// numberP1 = &number2; // OK,允许重新指向【常量指针】存放的地址

// 指针常量
int* const numberP2 = &number;
*numberP2 = 100; // OK,允许去修改【指针常量】存放地址所对应的值
// numberP2 = &number2; // 报错,不允许重新指向【指针常量】存放的地址

// 常量指针常量
const int * const numberP3 = &number;
// *numberP3 = 100; // 报错,不允许去修改【常量指针常量】存放地址所对应的值
// numberP3 = &number2; // 报错,不允许重新指向【常量指针常量】存放的地址

return 0;
}