前言
b站C++课程学习笔记整理。
b站视频: 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难
84-88. 内存四区和new的用法
C++程序在执行时,将内存分为四个区:
- 编译后:
- 代码区:存放函数体的二进制代码,由操作系统进行管理。
- 全局区:存放全局变量和静态变量以及全局常量(常量由const修饰)。
- 程序运行后:
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等。
- 堆区:由程序员分配和释放,若程序员不释放,程序运行后自动释放。
总结:
- 局部的在栈区,全局的在全局区。
- 和Java类似。只不过Java没有指针的功能。
- 不要返回局部变量的地址,因为局部的变量在函数执行完后已经被编译器释放了(但是我VS2022没报错并成功了不知道为啥)。
- 可以使用
new
关键字开辟堆空间(类似于Java)。这个可以返回地址。
- 可以使用
delete
关键字释放堆空间。
感觉是新版的编译器对这个地方做了很多的优化。
int* p = new int(10);
注意new出来的是返回的地址,要用指针接收。
89- 90. 引用
91. 引用做函数参数
作用:函数传递时,可以利用引用的技术让形参修饰实参。
优点:可以简化地址传递修改实参。
其实引用的nickname也是指针。和指针的区别是他可以直接用,不需要解引用*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
| #include<iostream>; using namespace std;
void swap1(int* p_a,int* p_b) { int temp = *p_a; *p_a = *p_b; *p_b = temp; }
void swap2(int &c,int &d) { int temp = c; c = d; d = temp; }
int main() { int a = 10; int b = 20;
cout << "调用前a是" << a << "\tb是" << b << endl; swap1(&a, &b); cout << "调用后a是" << a << "\tb是" << b << endl;
int c = 10; int d = 20; cout << "调用前c是" << c << "\td是" << d << endl; swap2(c,d); cout << "调用后c是" << c << "\td是" << d << endl; return 0; }
|
92. 引用做函数的返回值
- 不要返回局部变量的引用。
- 函数的调用可以作为左值。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include<iostream>; using namespace std;
int& printNum(int& a) { return a; }
int main() { int a = 0; printNum(a) = 20; cout << a << endl; return 0; }
|
93. 引用的本质
本质:引用的本质在c++内部实现一个指针常量(指针指向的位置不可改,指针指向的值可以改)。
94. 常量引用
常量引用主要用来修饰形参,防止误操作。
在函数形参列表中,可以加const
修饰形参,防止形参改变实参。
const int& ref = 10
代表着int temp = 10; const int& ref = temp
;
使用const
修饰形参防止误操作:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include<iostream>; using namespace std;
void printNum(const int& a) { cout <<"形参为" << a << endl; }
int main() { int a = 0; printNum(a); cout <<"实参为"<< a << endl; return 0; }
|
95. 函数默认参数
c++中函数形参可以有默认值。
注意的点:
- 如果某个位置已经有了默认参数,那么从超过这个位置往后从左到右都必须由默认值。
- 如果函数声明有默认参数,函数的实现就不能有默认参数。声明和实现只能有一个有默认参数。
96. 函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用时必须填补这个位置。占位参数可以有默认参数。
语法:返回值类型 函数名(数据类型){}
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<iostream>; using namespace std;
void printNum(int a, int) {
} void printNum2(int a, int = 10) { }
int main() { int a = 0; printNum(a,10); printNum2(a); return 0; }
|
97. 函数重载
就是Java中的方法重载,一样的。
98. 函数重载注意事项
当引用作为重载条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include<iostream>; using namespace std;
void printNum(int &a) { cout<<"不加const的函数被调用" << endl; } void printNum(const int &a) { cout << "加了const的函数被调用" << endl; }
int main() { int a = 10; printNum(a);
printNum(10); return 0; }
|
尽量避免函数重载碰到函数默认参数产生二义性:
99-100. 类和对象-封装-属性和行为做为整体
面向对象的三大特征:封装、继承和多态。
封装:将属性和整体作为一个整体,表现生活中的事物。
语法:class 类名 { 访问权限: 属性 / 行为}
。
跟Java区别是权限符是在类里面写的。
示例:设计一个学生类,包含姓名学号get, set方法等(经典老番)。
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
| #include<iostream>; using namespace std;
class student { public: string s_name; int s_age;
student() {}
student(string name, int age) { s_name = name; s_age = age; }
void setName(string name) { s_name = name; } void setAge(int age) { s_age = age; } string getName() { return s_name; } int getAge() { return s_age; }
void study() { cout << "学生可以学习" << endl; } };
int main() { student s1; student* p= new student("孙健耕", 21); s1 = *p; cout << "年龄是" << s1.getAge() << endl;
student s2 = student("孙健耕", 22); cout << "年龄是" << s2.getAge() << endl; }
|
101. 访问权限
102. class和struct区别
其实这俩功能很像。唯一区别:
struct
默认是public。
class
默认是private。
103. 成员属性设置为私有
优点:可以自己控制读写权限。对于写权限,可以查看数据的有效性。
106. 对象的初始化和清理
初始化:构造函数。
清理对象的内容:析构函数。
编译器会默认提供一个默认的空的构造函数和空的析构函数。
107. 构造函数的分类和调用
两种分类方式:
- 按参数,分为:有参构造和无参构造。
- 按类型,分为:普通构造和拷贝构造。
三种调用方法:
注意:使用无参构造的时候不要写小括号,否则编译器会认为这是一个函数的声明。
示例代码:
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
| #include <iostream> using namespace std;
class student { public: string s_name; int s_age;
student() { cout << "无参方法被调用" << endl; }; student(string name,int age) { cout << "带参方法被调用" << endl; }; student(string name) { cout << "带参方法被调用" << endl; }; ~student() { cout << "析构函数被调用" << endl; };
student(const student &s) { s_name = s.s_name; s_age = s.s_age; cout << "拷贝方法被调用" << endl; }; };
int main(){ student s1; student s2("孙健耕", 21); student s3(s2); student s4 = student(); student s5 = student("孙健耕", 21); student s6 = student(s5);
student();
system("pause"); return 0; }
|
108. 拷贝构造函数的调用
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
| #include <iostream> using namespace std;
class student { public: string s_name; int s_age;
student() { cout << "无参方法被调用" << endl; }; student(string name,int age) { cout << "带参方法被调用" << endl; }; student(string name) { cout << "带参方法被调用" << endl; }; ~student() { cout << "析构函数被调用" << endl; };
student(const student &s) { s_name = s.s_name; s_age = s.s_age; cout << "拷贝方法被调用" << endl; }; }; student doWork(student s) { return s; };
int main(){
student s1; student s2(s1); doWork(s1);
system("pause"); return 0; }
|
109. 构造函数的调用规则
默认情况下,编译器至少给一个类添加三个函数
- 默认构造函数(无参,函数体为空)。
- 默认析构函数(无参,函数体为空)。
- 默认拷贝构造函数,对属性进行值拷贝。
- 和Java一样,如果用户定义有参构造,c++不会提供无参构造,但是会提供一个拷贝。
- 如果用户定义拷贝构造,那C++不会提供任何别的默认构造方式了。
110. 深拷贝与浅拷贝
解决:使用自定义的拷贝函数进行深拷贝。
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
| #include <iostream> using namespace std;
class student { public: string s_name; int* s_age; student() { cout << "无参构造" << endl; } student(string name,int age) { cout << "带参构造" << endl; s_name = name; s_age = new int(age); }
student(const student &s) { s_name = s.s_name; s_age = new int(*s.s_age); }
~student() { cout << "析构函数" << endl; if (s_age != NULL) { delete s_age; s_age = NULL; } } }; void test() { student s1("孙健耕", 21); student s2(s1); } int main() {
test(); system("pause"); return 0; }
|
111. 初始化列表
作用:
C++提供了初始化列表的语法,用来初始化属性。
语法:构造函数(): 属性1(值1),属性2(值2) ...{}
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> using namespace std;
class student { public: string s_name; int s_age;
student(string name, int age) :s_name(name), s_age(age) { cout << "初始化列表构造方法被调用" << endl; }
}; void test() { student s1("孙健耕", 21); cout << s1.s_age << s1.s_name << endl; } int main() {
test();
system("pause"); return 0; }
|
112. 类对象作为类成员
C++类中的成员可以实另一个类的对象。
例如:
1 2 3 4
| class A{} class B{ A a; }
|
当创建B对象时,A与B的构造与析构的顺序是?
构造:先创建一个A类对象,再来构造B类。
析构:先析构B类,再析构A类。
113. 静态成员
分为静态成员变量和静态成员函数。
- 静态成员变量
- 所有对象共享同一份数据
- 再编译阶段分享内存
- 类内声明,类外初始化
比如:动态的是不同的对象之间可能不同的数据。静态的是不同的对象间肯定相同的数据。const
是不同对象间肯定相同,且无法修改的数据。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> using namespace std;
class student { public: static string s_name; }; string student::s_name = "Kalle"; void test() { student s1; cout << s1.s_name << endl; student s2; s2.s_name = "Jack"; cout << s1.s_name << endl; cout << student::s_name << endl; } int main() {
test(); system("pause"); 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 25 26 27 28 29
| #include <iostream> using namespace std;
class student { public: static string s_name; static void dowork() { s_name = "Jack"; } }; string student::s_name = "Kalle"; void test() { student s1; cout << s1.s_name << endl;
student::dowork(); cout << s1.s_name << endl;
} int main() {
test(); system("pause"); return 0; }
|
注意,静态成员方法是可以有访问权限要求的。
114. 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态的成员变量才属于类的对象上。
- 空对象(类是空的)占一个字节(目的是区分不同的空对象)。
- 非对象中:
- 成员变量按类型占相应的字节。(比如一个int占4个,两个int成员变量占8个)
- 成员方法、以及静态的变量和方法都不属于类的对象上。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> using namespace std;
class student { public: int name; int age; };
void test() { student s1; cout << sizeof(s1) << endl; } int main() {
test(); system("pause"); return 0; }
|
115. this指针的用途
- 解决变量重名(和Java一样)。不同的是C++里this是指针,指向的是调用这个指针的对象。所以应该用指针的方式去使用this。例如:
this->name = name
- 利用解引用的方式返回本体。 例如:
return *this;
示例代码:
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
| #include <iostream> using namespace std;
class student { public: int age;
student(int age) { this->age = age; }
student& addAge(student &s) { this->age += s.age; return *this; } };
void test() { student s1(10); cout << s1.age << endl; } void test2() { student s2(10); student s3(10); s2.addAge(s3).addAge(s3).addAge(s3).addAge(s3).addAge(s3); cout << s2.age << endl; } int main() {
test2(); system("pause"); return 0; }
|
注意在返回自身的引用的函数addAge
中,
- 参数使用引用的方式,是因为这样做不需要调用系统的默认浅拷贝函数,直接通过引用(本质是个指针),进行地址操作,节省空间。
- 返回值使用引用的方式,是因为这样做不需要调用系统的默认浅拷贝函数。否则就会返回一个拷贝后的新的
student
,虽然和s2
是一模一样,但是他们本质上是两个对象。这样的返回值是20,而不是正确的60。
116. 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是要注意有没有用到this指针。如果用到了this指针,就要判断一下,保证代码的健壮性。
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
| #include <iostream> using namespace std;
class student { public: int age;
student(int age) { this->age = age; }
void showAge() { cout << "成员函数被调用" << endl; } void showAge1() { if (this == NULL) { return; } cout << age << endl; } };
void test() { student* p = NULL; p->showAge(); p->showAge1(); }
int main() {
test(); system("pause"); return 0; }
|
117. const修饰成员函数
常函数:
- 成员函数后加
const
后称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性加
mutable
关键字后,依然可以修改
常对象:
- 声明对象前加
const
称该对象为常对象
- 常对象只能调用常函数
示例代码:
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
| #include <iostream> using namespace std;
class student { public: int age; mutable int number;
student(int age,int number) { this->age = age; this->number = number; }
void showAge() const{ number = 20; this->number = 30; } void showAge1() {
} };
void test() { const student s = student(10,20); s.showAge(); s.number = 20;
}
int main() {
test(); system("pause"); return 0; }
|
118. 友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,这时就需要友元技术。
关键字friend
。
三种实现:
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
| #include <iostream> using namespace std;
class building { friend void myFriend(building& b); public: string sittingroom; private: string bedroom; public: building() { sittingroom = "客厅"; bedroom = "卧室"; } };
void myFriend(building &b) { cout << "正在访问:" << b.sittingroom<<endl; cout << "正在访问:" << b.bedroom << endl; } int main() { building b = building(); myFriend(b); system("pause"); return 0; }
|
119. 类做友元
让一个类可以访问另一个类中的所有的成员内容。
构造函数也可以在class外构造,通过域访问。
示例代码:
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
| #include <iostream> using namespace std;
class Building; class GoodFriend { public: GoodFriend(); Building* building; void visit(); };
class Building { friend class GoodFriend; public: Building(); private: string bedroom; public: string sittingroom; };
Building::Building() { bedroom = "卧室"; sittingroom = "客厅"; }
GoodFriend::GoodFriend() { building = new Building; }
void GoodFriend::visit() { cout << "好兄弟正在访问:" << building->sittingroom << endl; cout << "好兄弟正在访问:" << building->bedroom << endl; }
int main() { GoodFriend f = GoodFriend(); f.visit(); system("pause"); return 0; }
|
120. 成员函数做友元
不是一整个类,而是让某个类中的某个成员函数可以做友元:
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
| #include <iostream> using namespace std;
class Building; class GoodFriend { public: GoodFriend(); Building* building; void visit(); };
class Building { friend void GoodFriend::visit(); public: Building(); private: string bedroom; public: string sittingroom; };
Building::Building() { bedroom = "卧室"; sittingroom = "客厅"; }
GoodFriend::GoodFriend() { building = new Building; }
void GoodFriend::visit() { cout << "好兄弟正在访问:" << building->sittingroom << endl; cout << "好兄弟正在访问:" << building->bedroom << endl; }
int main() { GoodFriend f = GoodFriend(); f.visit(); system("pause"); return 0; }
|
121. 加号运算符重载
示例代码:
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
| #include <iostream> using namespace std;
class person { public: int num; int age;
person() { };
person(int num, int age) { this->age = age; this->num = num; };
person operator+ (person& p) { person temp; temp.age = this->age + p.age; temp.num = this->num + p.num; return temp; } };
void test() { person p1 = person(10, 20); person p2 = person(30, 40);
person p3 = p1 + p2; cout << "age是" << p3.age << "num是" << p3.num << endl; } int main() { test(); system("pause"); return 0; }
|
有类内和类外两种定义方式。