cpp基础
参考连接:
- 学校课程23版 https://www.bilibili.com/video/BV1WG411d7ui/?pop_share=1&vd_source=2ea044642ac564524fdce3b964e1b2ae
- 课本教材 https://github.com/ShujiaHuang/Cpp-Primer-Plus-6th
第一讲
程序运行流程
编译-运行(从硬盘到内存再一条一条给CPU执行)
- 1、
内存
很重要 - 2、程序存储在硬盘上
- 3、在CPU上一条一条指令执行
注意:为什么中间加一步内存
?---硬盘读取速率太慢了
四大金刚 - 数据结构 - 算法分析 - 操作系统 - 计算机网络
内存中的存储形式:二进制数
地址的概念
32位操作系统是2^32个地址
64位操作系统同理
编译原理
compiling vs interpretation
编译器将源代码编译成机器码,而解释器则将源代码直接运行在计算机上
第二讲
data types
1 |
|
1 |
|
1 |
|
条件表达式
在计算机中,0 代表假,非 0 代表真
1 |
|
if 判断语句
这里就不记代码了
循环语句
1 |
|
for 循环与 while 循环类似,可以相互转换
这里就不记代码了,偷点懒...
循环嵌套
略...
break 与 continue
break
表示结束当前循环,continue
表示跳过本次循环
switch 与 case
选择语句执行,需要配合break
食用
第二讲实验记录
第一题写的不好,一开始看错题了,回去好好改一下
请注意,本题的目标是对原始的字符数组进行修正!!!
1 |
|
这里优化了一下:
但是实际上这里还有有问题,对于原始数组还是没有进行完全的修改(\0后面还有字符,只是没有输出出来!!!)
1 |
|
最终的版本:这下应该是比较合理的了
1 |
|
第二题:本题比较简单,没有什么坑,只需要遍历一遍数组,然后根据比较的结果进行替换即可
1 |
|
第三问感觉写的也有点问题,有点局限了,没有考虑特殊情况
如果这个数组中出现了两个出现次数为奇数
的情况怎么办???
这种情况下就不能简答的使用异或操作了,因为两个奇数异或的结果是0
1 |
|
第三讲
array
1 |
|
1 |
|
1 |
|
1 |
|
char[] 的比较:a == b 比较的是数组的地址,而不是数组的内容
string 的比较:c == d 比较的是字符串的内容,因为 string 类重载了 == 运算符
1 |
|
第四讲
Function
1 |
|
1 |
|
将循环用递归来表示: 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//循环
int iteration(int a)
{
int sum;
for(sum=1;a>1;a--)
{
sum *= a;
}
return sum;
}
//递归
int recursion(int a)
{
if(a==1)
return 1;
return a*recursion(a-1);
}
// 函数重载
int add(int a,int b){
return a+b;
}
float add(float a,float b=3.3f){
return a+b;
}
void main(){ //开辟栈区域
cout<<iteration(5)<<endl;
cout<<recursion(5)<<endl;
cout<<add(1,2)<<endl;
cout<<add(1.1f,2.2f)<<endl;
cout<<add(1.1f)<<endl; //b是默认值
}
1 |
|
指针基础 ⭐⭐⭐⭐⭐
1 |
|
1 |
|
指针数组和数组指针的区别一定要搞懂!!! 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;
int add(int x, int y)
{
return x + y;
}
int mul(int x, int y)
{
return x * y;
}
void main()
{
int c[3][2] = { {1,2},{3,4},{5,6} };
int(*p6)[2] = c; //数组指针 指向连续数组的指针
int* p5[3] = { c[0],c[1],c[2] };
int** p7 = p5; // 指针数组:指向不连续数组的指针
// 上述概念存疑,待定
//二维数组可以理解为数组指针!!!
//二重指针不一点是连续的,这是与数组的区别,证明如下:
int* p8 = &c[0][0];
for (int i = 0;i < 3;++i)
{
for (int j = 0;j < 2;++j)
{
cout << p8[i * 2 + j];
}
}
cout << endl;
//函数的指针
int(*f)(int, int);
f = add; //回调函数,实现的原理就是函数指针
cout << f(2, 3) << endl;
f = mul;
cout << f(2, 3) << endl;
}
1 |
|
1 |
|
第三讲实验记录
第三次实验课只有一道题,但是这题感觉难度比较大,因为老师的要求比较高,一开始使用的第二种方法,需要两次遍历加上循环输出,老师认为还有更好的方法,所以尝试使用了第一种,只需要一次遍历,但是使用到了两个指针,类似快慢指针的解法,老师认为还可以优化,后面就没有好的想法了,后面复习的话需要认真看看这道题!
1 |
|
第四讲实验记录
1 |
|
1 |
|
1 |
|
第五讲实验记录
第五讲
栈空间 - 函数调用的时候,函数的参数会复制一份,然后调用函数,函数执行完毕,函数的参数就结束了,函数的返回值也会复制一份,然后返回到调用者,调用者再根据返回值进行赋值。函数也可以没有返回值
1 |
|
指针基础
1 |
|
指针与函数的结合应用 ⭐⭐⭐⭐⭐ 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
39int fun1(int x)
{
x += 2;
return x;
}
int fun2(int x[])
{
x[0] += 2;
return *x;
}
int* fun3(int* p) {
*p += 2;
return p;
}
int* fun4(int x) {
x += 2;
int* p = &x;
return p;
}
void main()
{
int d = 1,e = 0,*f = NULL;
e = fun1(d);
cout<<"d ="<<d<<" e ="<<e<<endl; // d=1 e=3
d = 1;
e = fun2(&d); //传递的是d的地址,所以e=3
cout<<"d ="<<d<<" e ="<<e<<endl; // d=3 e=3
d = 1;
f = fun3(&d);
cout<<"d ="<<d<<" f ="<<*f<<endl; // d=3 f=3
d = 1;
f = fun4(d);
cout << "d =" << d << " f =" << *f << endl; // d=1 f 是一个不知道的值,表示临时变量 x 的地址,fun4 栈空间结束后,f 的值是随机的
// 注意,在release模式下,f 的值是3,release模式下不会主动清楚找不到的指针
}
堆空间:程序在,堆空间就在,程序结束了,堆空间就结束了
c语言分配堆空间:malloc 函数
c语言程序中,释放堆空间:free函数, malloc 生成空间后必须用free释放,防止内存过大
在cpp中,使用new
和delete
来实现堆空间的分配和释放
1 |
|
1 |
|
memory leak
内存泄漏是指程序在申请内存后,无法释放已分配的内存空间,导致程序运行过程中可用内存逐渐减少的现象。长时间运行或大量数据处理的应用程序特别容易出现内存泄漏问题。
引用数据类型
1 |
|
int& 是引用数据类型 , b 是 a 的引用;
引用数据类型的本质是:给变量 a 所在内存赋予另外一个别名 b
引用数据类型的使用方法 : 直接当做原来的变量使用即可, 可以替换原来变量的位置使用
1 |
|
第六讲实验记录
第一题: 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150#include<iostream>
#include<string>
using namespace std;
struct stu {
string name;
int id;
char gender;
int age;
};
// 判断学号是否冲突
bool isIDConflict(int newID, stu student[], int count) {
for (int i = 0; i < count; i++) {
if (student[i].id == newID) {
return true;
}
}
return false;
}
// 添加学生
void Addstu(stu student[], int& count) {
cout << "请输入学生姓名:";
cin >> student[count].name;
int newID;
cout << "请输入学生学号(确保唯一):";
cin >> newID;
while (isIDConflict(newID, student, count)) {
cout << "该学号已存在,请重新输入学生学号:";
cin >> newID;
}
student[count].id = newID;
cout << "请输入学生性别:";
cin >> student[count].gender;
cout << "请输入学生年龄:";
cin >> student[count].age;
count++;
}
// 查找学生
void Findstu(int ID, stu student[], int count) {
for (int i = 0; i < count; i++) {
if (student[i].id == ID) {
cout << "学生姓名:" << student[i].name << endl;
cout << "学生学号:" << student[i].id << endl;
cout << "学生性别:" << student[i].gender << endl;
cout << "学生年龄:" << student[i].age << endl;
break;
}
if (i == count - 1) {
cout << "未找到该学号的学生。\n" << endl;
}
}
}
// 修改学生
void Modifystu(int ID, stu student[], int count) {
for (int i = 0; i < count; i++) {
if (student[i].id == ID) {
cout << "请输入新的学生姓名:";
cin >> student[i].name;
cout << "请输入新的学生性别:";
cin >> student[i].gender;
cout << "请输入新的学生年龄:";
cin >> student[i].age;
break;
}
if (i == count - 1) {
cout << "未找到该学号的学生,无法修改。\n" << endl;
}
}
}
// 删除学生
void Deletestu(int ID, stu student[], int& count) {
for (int i = 0; i < count; i++) {
if (student[i].id == ID) {
for (int j = i; j < count - 1; j++) {
student[j] = student[j + 1];
}
count--;
break;
}
if (i == count - 1) {
cout << "未找到该学号的学生,无法删除。\n" << endl;
}
}
}
// 显示所有学生
void Showstu(stu student[], int count) {
if (count == 0) {
cout << "目前没有学生信息。\n" << endl;
}
else {
for (int i = 0; i < count; i++) {
cout << "学生姓名:" << student[i].name << endl;
cout << "学生学号:" << student[i].id << endl;
cout << "学生性别:" << student[i].gender << endl;
cout << "学生年龄:" << student[i].age << endl;
cout << "------------------------" << endl;
}
}
}
int main() {
stu student[20];
int studentCount = 0;
int choice;
int studentID;
while (true) {
cout << "1. 添加学生" << endl;
cout << "2. 查找学生" << endl;
cout << "3. 修改学生" << endl;
cout << "4. 删除学生" << endl;
cout << "5. 显示所有学生" << endl;
cout << "6. 退出" << endl;
cout << "请输入你的选择:";
cin >> choice;
switch (choice) {
case 1:
Addstu(student, studentCount);
break;
case 2:
cout << "请输入要查找的学生学号:";
cin >> studentID;
Findstu(studentID, student, studentCount);
break;
case 3:
cout << "请输入要修改的学生学号:";
cin >> studentID;
Modifystu(studentID, student, studentCount);
break;
case 4:
cout << "请输入要删除的学生学号:";
cin >> studentID;
Deletestu(studentID, student, studentCount);
break;
case 5:
Showstu(student, studentCount);
break;
case 6:
return 0;
default:
cout << "无效选择,请重新输入。" << endl;
}
}
}
第二题 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#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> fun(vector<int>& nums) {
vector<vector<int>> result;
int n = nums.size();
if (n < 3) return result; // 如果数组长度小于3
sort(nums.begin(), nums.end()); // 先对数组进行排序
for (int i = 0; i < n - 2; ++i) {
// 跳过重复元素
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
++left; // 如果总和小于0,移动左指针以增加总和
}
else if (sum > 0) {
--right; // 如果总和大于0,移动右指针以减少总和
}
else {
// 找到一个解
result.push_back({ nums[i], nums[left], nums[right] });
// 跳过重复元素
while (left < right && nums[left] == nums[left + 1]) ++left;
while (left < right && nums[right] == nums[right - 1]) --right;
++left;
--right;
}
}
}
return result;
}
int main() {
vector<int> x = { -1, 0, 1, 2, -1, -4,-1,1,0};
vector<vector<int>> results = fun(x);
for (const auto& result : results) {
cout << '[';
for (size_t i = 0; i < result.size(); ++i) {
cout << result[i];
if (i < result.size() - 1) cout << ", ";
}
cout << ']' << endl;
}
return 0;
}
第六讲
结构体:结构体只能定义变量,没有方法
1 |
|
内存对齐的概念:
结构体中的每一个属性在内存中需要实现内存对齐,即按照最长的字节长度进行填补
在上面的例子中,对象 a 的内存是12个字节
但是用上面结构体 B 创建的对象,只需要占用8字节内存,节约了内存
1 |
|
对于结构体的定义已经对象的创建可以简化如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef struct Teacher{
string name;
int age;
char gender;
}T;
void main()
{
T t = {"lisi",20,'F'}; // 创建对象并初始化
cout<<t.name<<endl;
cout<<t.age<<endl;
cout<<t.gender<<endl;
//创建结构体数组
T t[2] = {"zhangsan",35,'M'}, {"lisi",20,'F'};
}
创建指针结构体 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20typedef struct Teacher{
string name;
int age;
char gender;
}T;
void main()
{
T p = {"zhangsan",35,'M'};
T* p1 = &p;
cout<<p1->name<<endl; //注意对于指针结构体的属性访问使用不同的方法
cout<<p1->age<<endl;
cout<<p1->gender<<endl;
//类似于堆的创建方法
T* p2 = new T; //创建一个对象,并分配内存,返回的是首地址
//等价
p1->name = (*p1).name;
}
结构体与函数的结合应用 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25typedef struct Teacher{
string name;
int age;
char gender;
}T;
void fun1(T t)
{
t.name = "lisi";
}
void fun2(T* t)
{
t->name = "lisi";
}
void main()
{
T t1 = {"zhangsan",35,'M'};
fun1(t1);
cout<<t1.name<<endl; // zhangsan 注意这里还是zhangsan 不变,栈空间的参数发生了改变,但是不影响main空间的参数
fun2(&t1);
cout<<t1.name<<endl; // lisi 这里已经变成lisi了,因为指针的参数发生了改变,所以main空间中的参数也发生了改变
}
共用体:多个变量共享一个内存空间,解决内存不足的问题,已经被淘汰
1 |
|
第七讲 面向对象编程
类与对象 ⭐⭐⭐⭐⭐
定义一个类,类除了有属性,还有行为(函数) 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
28class Student{ //类名
public: //使用public,表示属性和函数都可以被外部访问
int age; // 属性
char gender;
string name;
void show1(){ //方法
cout<<name<<","<<gender<<","<<age<<endl;
}
void show2();//只声明函数,后面再定义具体的内容
};
void Student::show2(){
cout<<name<<","<<gender<<","<<age<<endl;
}
void main()
{
Student s1;
s1.age=18;
s1.gender='m';
s1.name="zhangsan";
s1.show1();
s1.show2();
}
构造函数:给对象赋值 ⭐⭐⭐⭐⭐
构造函数的两个特点: - 不需要返回值 - 函数的名字与类名保持一致
1 |
|
一共有7种形式的构造函数,需要记住
当我们创建一个类时,系统会默认帮助我们创建一个无参构造函数和一个拷贝构造函数
但是当写了带参构造函数后,默认的无参构造函数和拷贝构造函数就没有了,必须自己加上
当写了拷贝构造函数,默认的无参构造和带参构造就没有了
下面的输出结果是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
30#include<iostream>
#include<cctype>
using namespace std;
class Student {
public:
int id;
//Student() {
// id = 0;
// cout << "Student()" << endl;
//}
//Student(int _id) {
// id = _id;
// cout << "Student(int _id)" << endl;
//}
//Student(const Student& s) {
// id = s.id;
// cout << "Student(const Student & s)" << endl;
//}
};
void main()
{
Student s1 = Student();
cout << s1.id << endl;
}
例题分析:
1 |
|
析构函数 ⭐⭐⭐⭐⭐
析构函数是一个成员函数,在对象超出范围或通过调用 delete 或 delete[] 显式销毁对象时,会自动调用析构函数。 析构函数与类同名,前面带有波形符 (~)
1 |
|
如果未定义析构函数,编译器会提供一个默认的析构函数;对于某些类来说,这就足够了。当类维护必须显式释放的资源(例如系统资源的句柄,或指向在类的实例被销毁时应释放的内存的指针)时,你需要定义一个自定义的析构函数。
第八讲
深拷贝和浅拷贝 ⭐⭐⭐⭐⭐
1 |
|
使用深拷贝还是浅拷贝,需要根据实际情况来决定!!!
初始化列表 ⭐⭐⭐
1 |
|
初始化列表可以提高效率,原理不是很重要
this指针 与 连锁调用 ⭐⭐⭐⭐⭐
this最大的作用,将当前对象与类中的对应调用的函数联系起来,this指针指向调用这个函数的对象,this指针传入调用的函数(没有写出来),从而实现了链接
1 |
|
第九讲
const 关键字 ⭐
1 |
|
static关键字原理 ⭐⭐
1 |
|
1 |
|
分析对象的大小
总结:
- 当一个对象没有任何属性和方法时,占用内存 1 字节
- 类中的static属性不占用任何空间,static属性单独存储
- 类中的任何函数(动态函数和静态函数)都不占用类的空间存储
1 |
|
friend 关键字 ⭐⭐
总结:
- 友元函数需要注意下面三种定义形式
1 |
|
第十讲
面向对象编程的第二特性---继承(inheritance)
面向对象编程的三大特性:封装、继承、多态
特性 | 核心思想 | 优点 |
---|---|---|
封装 | 隐藏内部实现,暴露必要接口 | 提高安全性,降低耦合性 |
继承 | 子类继承父类的属性和方法,支持代码复用和扩展 | 提高代码复用性,支持层次化设计 |
多态 | 同一接口在不同对象中有不同实现 | 提高灵活性和可扩展性,支持面向接口编程 |
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在子类中添加新的属性和方法,或者重写父类的方法,提高代码的复用性
子类继承父类,会继承父类的所有成员变量和成员函数,同时子类也可以添加新的属性和方法,或者重写父类的方法
1 |
|
权限控制的三个关键字 ⭐⭐⭐
private::私有属性,其他对象无法访问
protected::受保护属性,其他对象无法访问,但是子类可以访问
public::公共属性,其他对象都可以访问
1 |
|
三种继承方式 ⭐⭐⭐⭐
1 |
|
继承中的构造函数和析构函数顺序
子类、父类的构造函数只负责创建自己的属性,析构函数同理,但是顺序先后有所不同,需要注意:
- 构造函数是父类先、子类后
- 析构函数是子类先、父类后
类似数据结构:栈
1 |
|
继承的思想:is a 1
class son : public Father{}
或者另一种方式:包含的方式 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Son{
public:
Father s;
Son() {
cout << "Son()" << endl;
}
~Son() {
cout << "~Son()" << endl;
}
};
Son s1;
s1.s.a = 10; //访问父类的属性,比较繁琐
override ⭐⭐⭐⭐⭐
子类继承父类后,可以重写该方法
1 |
|
子类 Son 重写了父类 Father 的同名函数 study(),导致父类的所有重载函数被隐藏。这是 C++ 的一个特性,称为 名称隐藏(Name Hiding)
多重继承 ⭐⭐⭐
1 |
|
第十一讲
菱形继承 ⭐⭐⭐⭐⭐
尽量不要用,关系比较复杂
虚继承
虚继承指的的并不是真的继承父类的属性,而是子类一个指针指向父类的对应属性,因此可以实现多个指针指向一个属性(地址),实现了多重继承,子类和父类在内存上是共用的
虚继承是为了解决菱形继承问题而提出的
1 |
|
下面使用虚继承的方式来解决上述问题
1 |
|
Yang的一个指针指向父类的属性age,Tuo的一个指针也指向父类的属性age,这样 YangTuo 无论从哪边访问属性age都不会产生歧义
加法运算操作符重载 ⭐⭐⭐⭐⭐
操作符本质上都是函数,像 + - * / 数组([])等都是操作符
操作符重载就是改变操作符的功能(行为)
操作符重载可以有两种方法,第一种是在类里面定义,第二种是在类外面定义,同时存在时,第一种方法优先级更高
1 |
|
如果重载操作符返回的类型为引用,会产生内存错误,地址不存在,如下:
原因是,当返回的是对象时,会在外面进行一次赋值操作,接返回的内容,但是如果返回的是引用,该返回对象会随着栈空间的消失而不存在
1 |
|
但是上述代码在我自己的电脑上运行都是输出36,并没有和老师上课讲的内容一致,我也不知道为什么,但是上述分析的原理应该是没有问题的,可能电脑问题hhhhhh
关系运算符重载
这个比较简单
1 |
|
赋值运算符重载
下面涉及到了内存泄漏的一些东西,需要结合老师的视频来看
1 |
|
1 |
|
浅拷贝会造成重复删除的问题
第十二讲
左移运算符重载
重载输出的符号 “<<”
1 |
|
自增运算符重载
1 |
|
仿函数 functor
functor 函数是对象
能够实现的功能有:可以保存函数执行过程中的中间变量
就是对 括号 的重载
1 |
|
第十三讲
面向对象编程的第三大特性---多态 polymorphism ⭐⭐⭐⭐⭐
多态:能否将父类的对象转为子类的对象?
结论:
- 父类对象直接等于子类对象是可以的,但是没有变形成功
- 子类对象直接等于父类对象是不行的,会报错
多态分为动态多态和静态多态
函数重载就是静态多态
实现多态使用到的原理:
- 虚函数来实现晚绑定(虚函数与虚继承没有任何关系!)
- 对于父类的函数使用虚函数来实现就可以实现多态
实现多态的三大要点:
- 父类对象直接等于子类对象是不行的,要父类的 指针或者引用 等于子类对象才行
- 使用虚函数实现晚绑定
- 子类需要重写相关函数
指针和引用是实现多态的关键!!!
1 |
|
如何实现多态?需要使用到虚函数
虚函数可以实现函数与类之间的晚绑定
辨析:虚继承
为了解决菱形继承
问题提出的,虚函数
则是为了实现晚绑定
1 |
|
纯虚函数和抽象类 ⭐⭐⭐⭐⭐
纯虚函数的定义:定义一个类时使用到了前面讲的虚函数,但是不指定任何类的行为,类似下面的定义形式
1 |
|
抽象类的定义:含有纯虚函数
的类叫抽象类
,不能用来创建对象,只能被继承
继承的子类必须重写纯虚函数,才能实现创建对象
1 |
|
虚析构函数 ⭐⭐⭐⭐⭐
在实现多态时可能会存在内存泄漏的问题,原因是因为在删除堆空间的子类时,调用的是父类的析构函数,而父类的析构函数并没有释放子类中的堆空间,所以导致内存泄漏。
因此需要在父类的析构函数中添加一个虚析构函数,这样就会调用子类的析构函数,从而释放子类中的堆空间。
同时需要注意一点就是父类的虚析构函数还不能是纯虚析构(必须定义具体的行为),因为虚构函数是有意义的,无论是子类还是父类
1 |
|
第十四讲 模板和异常
函数模板 ⭐⭐⭐
函数模板的作用就是优化函数重载需要针对特定的数据类型,不是很方便的问题
函数模板可以 Template
函数模板解决的是函数中的传入参数不确定的情况
1 |
|
类模板 ⭐⭐⭐
类模板解决的是类中的属性不确定的情况
1 |
|
类模板 vector ⭐
vector是一个常见的类模板,下面介绍了一下常见的用法
1 |
|
Homework
HW_13
Write a geometry software using the idea of “polymorphism” so that the following code can run successfully.
1.Implement the 2-dimensional circle, rectangle, square, and regular triangle.
2.Implement the 3-dimensional sphere, cylinder, cuboid, cube, regular triangular prism, and tetrahedron.
3.Implement the method to calculate their areas (volumes).
Note: Design your own inheritance relationship and properties between each class. Search the formula for the area (volume) of each geometric shape with PI equal to 3.14159.
1 |
|
1 |
|
HW_12
Write your own “string” class. 1.The name of the class is “my_str”. 2.“my_str” has two properties, a “char*” representing the string and an “int” representing the length of the string. 3.Design your constructor and destructor. 4.Overload the “=” operator to avoid memory leaking. 5.Overload the “==” and “!=” operators so that we can know if two “my_str” objects are equal. 6.Overload the “<” and “>” operators so that two “my_str” objects can be compared in dictionary order, e.g. (“abcd” > “abbe”) is true. 7.Overload the “<<” operator so that “my_str” objects can be output directly instead of using the “object.property” form. 8.Overload the “+” operator so that two “my_str” objects can be concatenated together, e.g. “ab” + “cd” equals “abcd”.
1 |
|
HW_11
Write a class representing an array of “int”, which requires: 1.The name of the class is my_array. 2.my_array contains at least one property “arr” of type “int*”, which stores the elements of the array. 3.You are free to add more properties. 4.Design your constructor and destructor. 5.Overload the [] operator so that the object of my_array can return the elements at a specified position in “arr”. In addition, check if the position is out of bounds.
1 |
|
HW_10
Write a system for creating geometric shapes using Inheritance we just learned. 1.The system needs to support the 2-dimensional circle, rectangle, square, and regular triangle. 2.The system needs to support the 3-dimensional sphere, cylinder, cuboid, cube, and regular triangular prism. 3.Each shape needs to include two methods, perimeter (surface area) and area (volume). Note: Design your own inheritance relationship and properties between each class. You may search the formula to calculate the perimeter (surface area) and area (volume) of each geometric shape.
1 |
|
一些课外记录的笔记
程序设计的方法
- 结构化程序设计:顺序结构、选择结构、循环结构
- 面向对象程序设计
- 什么是对象:对象在现实世界中是一个实体或一种事物的概念,不关心对象的内部结构以及实现方法,仅关心它的功能和使用方法
- 面向对象的方法是利用
抽象
、封装
等机制,借助于对象、类、继承、消息传递
等概念进行软件系统构造的软件开发方法。
一个简单的C++语言
1 |
|
使用iostream的时候,必须使用namespace std,这样才能正确的使用命名空间std封装的标准程序库中的标识符cin、cout等
如何将一段代码变为可执行的程序,即如何编译编写好的程序:
1
g++ name.cpp -o name.exe
常量
常量指的是在程序执行过程中不会改变的量
一、字面常量
- 把常量书写到代码内部,叫字面常量 -
可以有整数(整型)、小数(实型),二者统称为数值型常量 -
字符型字面常量:由单引号包围的单个字符 -
字符串字面常量:以双引号包围的,任意个数个字符
1 |
|
二、符号常量
用标识符去定义的常量,给常量一个名字,就是符号常量 -
在c语言中使用编译预处理指令#define定义符号常量:#define PI 3.14 -
C++中在类型前添加关键词const来定义:const double PI=3.14 const int N = 6
- 符号常量的值必须在定义时指定,且不能赋新值
输入输出
cin数据输入
语法: 1
2数据类型 变量; //声明变量
cin >> 变量 //对变量进行赋值
1 |
|
cout打印输出
输出单份内容 1
2cout <<"Hello,World!"<< endl;
cout << 10 << endl;
输出多份内容 1
cout << "c++ is" << " the best "<< "programming language"<< endl;
注意:
非数字的内容必须使用 "" 包围
数字可以使用双引号,也可以不用
标识符和关键字
一、标识符:表示某类实体的符号(名称),简单理解为起名字
内容限定: - 只允许字母、数字、下划线的组合 - 数字不可开头 - 不可使用关键字
二、标识符的命名规范
命名的通用规范:
1、见名知意:任何场景
2、下划线命名法:变量命名
3、小驼峰:变量、函数(方法)命名
4、大驼峰:类命名
- 1、符号常量:
- 满足标识符的硬性要求下,若使用英文字母,应全部大写
- 2、变量:
- 满足标识符的硬性要求下,若使用英文字母,不应全部大写(大小写组合或纯小写)
三、标识符的限制规则 - 字母、数字、下划线组合,数字不开头 - 大小写敏感 - 不可使用关键字
关键字是内置使用的特殊标识符,用户不可占用
变量的基础应用
变量:在程序运行中,能存储计算结果或者能够表示值的抽象概念---记录数据
一、基本用法两步走: - 1、变量的声明(定义) - 语法:变量类型 变量名
1
int mun;
1
num=10;
1
int age = 10;
- 2、一次性声明多个变量
1
2
3
4int a,b,c;
a=10;
b=20;
c=30; - 3、结合1、2
1
int a=10,b=20,c=30;
三、常见的变量类型: 1
2
3
4int //整型
float //实型
char //字符型
string //字符串型
变量的基本特征
变量存储的数据是可以发生改变的,支持: - 多次赋值语句 - 各种数学运算
示例: 1
2
3
4
5
6
7
8
9
10int main(){
int age;
age = 10;
cout << age <<endl;
age = 11;
cout << age <<endl;
age=age+2
cout << age <<endl;
return 0;
}
数据类型-整型
short--短整型
int--整形
long--长整型
long long--长长整型
使用sizeof()可以求得数据具体占用的字节数 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#include <iostream>
using namespace std;
int main()
{
int a=10;
short b=10;
long c=10;
long long d=10;
cout<< sizeof(a) <<endl;
cout<< sizeof(b) <<endl;
cout<< sizeof(c) <<endl;
cout<< sizeof(d) <<endl;
return 0;
}
// 4
// 2
// 8 long 变量看机器不定
// 8
无符号和有符合数字
在c++中,数字是有无符号和有符号之分的 - 无符号:仅仅允许正数参在 -2(n-1)~2(n-1)-1 - 有符号:可以允许负数存在 0~ 2^n-1
1 |
|
数据类型-实型
实型数据默认有符号数
1 |
|
数据类型-字符串
C语言风格的字符串: 1
2char a[] = "Hello World"; //字符数组形式
char *b = "Hello World"; //指针形式1
string c = "Hello World";
在c++代码中可以使用c语言字符串
字符串的拼接
使用“+”进行连接:只能限定于字符串和字符串之间的拼接
C++中有to_string()
函数将内容转为字符串类型,对于非字符串需要先转为字符串!
C++中的字符串拼接示例: 1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
using namespace std;
int main()
{
string a = "疯狂星期四";
string b = "V me 50";
string c = a + "," + b;
cout << c << endl;
return 0;
}
// 疯狂星期四,V me 50
1 |
|
在 C 语言中,字符串实际上是以空字符 '\0'
结尾的字符数组。以下是常见的字符串拼接方法:
- 使用
strcat
函数strcat
函数位于<string.h>
头文件中。- 语法:
char *strcat(char *dest, const char *src)
。 - 示例:
1
2
3
4
5
6
7
8
9
10#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[] = " World";
strcat(str1, str2);
printf("%s\n", str1);
return 0;
} - 注意事项:
dest
数组要有足够的空间来容纳拼接后的字符串,否则会导致缓冲区溢出错误。strcat
函数会直接修改dest
所指向的字符串内容。
- 手动拼接
- 思路:通过遍历两个字符串,将第二个字符串的字符逐个复制到第一个字符串的末尾空字符之后。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include <stdio.h>
int main() {
char str1[20] = "Hello";
char str2[] = " World";
int i = 0, j = 0;
while (str1[i]!= '\0') {
i++;
}
while (str2[j]!= '\0') {
str1[i++] = str2[j++];
}
str1[i] = '\0';
printf("%s\n", str1);
return 0;
}
C 语言和 C++字符串拼接的区别
- 数据类型和安全性
- C 语言使用字符数组来表示字符串,容易出现缓冲区溢出等安全问题,因为它不进行边界检查。
- C++的
std::string
类会自动管理内存,进行边界检查,并且在拼接时会自动处理内存分配,相对更安全。
- 操作便捷性
- C
语言需要借助
strcat
函数或手动复制字符来实现拼接,代码相对繁琐。 - C++使用
+
运算符和append
方法,操作更加直观和便捷,代码更简洁易读。
- C
语言需要借助
- 头文件
- C
语言的字符串操作函数通常包含在
<string.h>
头文件中。 - C++的
std::string
相关操作包含在<string>
头文件中。
- C
语言的字符串操作函数通常包含在
数据类型-布尔型
在程序中表达互斥的概念
1 |
|
用数据类型bool
来表示
字符函数库 cctype
C++ 从 C 语言继承了一个与字符相关的、非常方便的函数软件包, 它可以简化诸如确定字符是否为大写字母、数字、标点符号等工作,这 些函数的原型是在头文件cctype(老式的风格中为 ctype.h)中定义的。 例如,如果 ch 是一个字母,则 isalpha(ch) 函数返回一个非零值,否则返回 0。同样,如果 ch 是标点符号(如逗号或句号),函数 ispunct(ch) 将返回 true。(注意这些函数的返回类型为 int,而不是 bool, 但通常 bool 转换能够将它们视为bool 类型)
- isalpha() 用来检查字符是否为字母字符;
- isdigit() 用来测试字符是否为数字字符(0~9),如 3;
- isspace() 用来测试字符是否为空白,包括换行符、空格和制表符;
- ispunct() 用来测试字符是否为标点符号。
1 |
|
运算符
C++内置的运算符有: - 算数运算符 - 赋值运算符 - 比较运算符 - 逻辑运算符 - 位运算符 - 三目运算符
一、算术运算符 - 加法运算符(+):
1
2
3int a = 5;
int b = 3;
int c = a + b; // c 的值为 81
int d = a - b; // d 的值为 2
1
int e = a * b; // e 的值为 15
1
int f = a / b; // f 的值为 1(因为是整数除法,结果取整)
1
int g = a % b; // g 的值为 2
二、赋值运算符 -
简单赋值运算符(=): 1
int x = 10;
1
2
3
4
5
6int y = 5;
y += 3; // 相当于 y = y + 3,y 的值变为 8
y -= 2; // y 的值变为 6
y *= 4; // y 的值变为 24
y /= 3; // y 的值变为 8
y %= 5; // y 的值变为 3
三、比较运算符 - 等于运算符(==):
1
bool result1 = (a == b); // 这里 result1 为 false,因为 5 不等于 3
1
bool result2 = (a!= b); // result2 为 true
1
bool result3 = (a > b); // result3 为 true
1
bool result4 = (a < b); // result4 为 false
1
bool result5 = (a >= b); // result5 为 true
1
bool result6 = (a <= b); // result6 为 false
四、逻辑运算符 -
逻辑与运算符(&&): 1
2
3bool p = true;
bool q = false;
bool r = p && q; // r 为 false,因为只有当两个操作数都为 true 时,结果才为 true1
bool s = p || q; // s 为 true,因为只要有一个操作数为 true,结果就为 true
1
bool t =!p; // t 为 false,因为!对操作数取反
- 其他表示方式
并不是所有的键盘都提供了用作逻辑运算符的符号
因此C++标准提供了另一种表示方式,如表所示,标识符and、or和not都是C++保留字,这意味着不能将它们用作变量名等。它们不是关键字,因为它们都是已有语言特性的另一种表示方式。另外,它们并不是C语言中的保 留字,但C语言程序可以将它们用作运算符,只要在程序中包含了头文件 iso646.h。
C++不要求使用头文件
1
2
3
4
5
6
7
8
9#include<iostream>
using namespace std;
void main()
{
int a = 1, b = 2;
if (a and b)
cout<<"hhh" << endl;
}
五、位运算符 -
按位与运算符(&): 1
2
3int m = 5; // 二进制为 0101
int n = 3; // 二进制为 0011
int o = m & n; // 二进制 0101 & 0011 = 0001,即 o 的值为 11
int p = m | n; // 二进制 0101 | 0011 = 0111,p 的值为 7
1
int q = m ^ n; // 二进制 0101 ^ 0011 = 0110,q 的值为 6
1
int r = m << 1; // 5 的二进制 0101 左移一位变为 1010,即 r 的值为 10
1
int s = m >> 1; // 5 的二进制 0101 右移一位变为 0010,s 的值为 2
六、三目运算符(也称为条件运算符):
1
int u = (a > b)? a : b; // 如果 a 大于 b,u 的值为 a,否则为 b
if逻辑判断语句
在
C++中,if
语句是一种基本的控制流结构,用于根据条件执行不同的代码块
1. 基本语法
1 |
|
这里的condition
是一个布尔表达式(结果为true
或false
)。如果condition
为true
,则执行花括号{}
内的代码块;如果为false
,则跳过该代码块。
例如:
1 |
|
在这个例子中,因为5
大于3
,所以条件x > 3
为true
,会执行输出语句
2. if-else
结构
1 |
|
当condition为true时执行if块中的代码,否则执行else块中的代码
例如:
1 |
|
这里因为2
不大于3
,所以会执行else
块中的代码。
3. if-else if-else
结构
1 |
|
可以根据多个条件进行判断,依次检查每个条件,直到找到一个为真的条件并执行相应的代码块,或者如果所有条件都为假,则执行最后的else
块中的代码。
例如:
1 |
|
在这个例子中,因为7
大于等于5
且小于10
,所以会执行第二个else if
块中的代码。
4. 嵌套的 if
语句
可以在if
、else
或else if
代码块中嵌套另一个if
语句。
例如:
1 |
|
这里首先判断a > 2
,如果为真,再判断b > 3
,如果也为真,则执行输出语句。
switch-case语句
在 C++ 中,switch
控制语句是一种多路分支结构,它允许根据一个表达式的值来选择一组语句执行。
基本语法介绍: 1
2
3
4
5
6
7
8
9
10
11switch (expression) {
case constant-expression1:
// 语句序列
break;
case constant-expression2:
// 语句序列
break;
// ...
default:
// 默认情况下的语句序列
}expression
必须能够产生一个整数或枚举类型的值。 - case
后面的
constant-expression
必须是编译时常量,并且每个
case
标签必须唯一。 - break
语句用来终止当前
case
并跳出整个 switch
语句。 - 如果没有
break
语句,程序会继续执行下一个 case
直到遇到
break
或 switch
结束。 - default
是可选的,如果没有匹配的 case
,则执行 default
下的语句。
根据用户输入的数字来显示相应的星期几名称 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;
int main() {
int day;
cout << "Enter a number from 1 to 7: ";
cin >> day;
switch (day) {
case 1:
cout << "Monday" << endl;
break;
case 2:
cout << "Tuesday" << endl;
break;
case 3:
cout << "Wednesday" << endl;
break;
case 4:
cout << "Thursday" << endl;
break;
case 5:
cout << "Friday" << endl;
break;
case 6:
cout << "Saturday" << endl;
break;
case 7:
cout << "Sunday" << endl;
break;
default:
cout << "Invalid input" << endl;
}
return 0;
}
定义一个枚举类型来表示星期几,并使用它作为 switch 表达式的类型
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>
using namespace std;
enum Weekday { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
int main() {
Weekday today = Thu;
switch (today) {
case Mon:
cout << "Today is Monday." << endl;
break;
case Tue:
cout << "Today is Tuesday." << endl;
break;
case Wed:
cout << "Today is Wednesday." << endl;
break;
case Thu:
cout << "Today is Thursday." << endl;
break;
case Fri:
cout << "Today is Friday." << endl;
break;
case Sat:
cout << "Today is Saturday." << endl;
break;
case Sun:
cout << "Today is Sunday." << endl;
break;
}
return 0;
}
枚举类型
在 C++ 中,枚举类型是一种数据类型,它允许我们定义一组命名的常量。枚举类型通常用于表示一个有限集合,例如一周中的每一天、月份、颜色等。枚举类型可以让你为整数常量赋予有意义的名字,这样可以使程序更具可读性和可维护性。
一、语法介绍 1
2
3
4
5enum enumeration-name {
enumerator1,
enumerator2,
// 更多枚举成员...
};enumerator
都是一个整数常量,默认情况下,第一个枚举成员的值为
0,后续的枚举成员的值依次递增 1。也可以显式地为枚举成员指定值:
1
2
3
4
5
6enum enumeration-name {
enumerator1 = 10,
enumerator2,
enumerator3 = 20,
enumerator4
};
二、声明变量并赋值 1
2
3
4enumeration-name variable_name;
variable_name = enumerator1; // 赋值
//或者
enumeration-name variable_name = enumerator1;
三、枚举类型的范围
默认情况下,枚举类型的底层类型取决于枚举成员的最大值。从 C++11 开始,可以指定枚举的底层类型:
1 |
|
四、枚举成员的访问
从 C++11 开始,我们可以使用作用域解析运算符 ::
来访问枚举成员: 1
Weekday today = Weekday::Friday;
如果使用的是 enum
而不是
enum class
,那么枚举成员会被提升到作用域内,可以直接使用它们的名字:
1
Weekday today = Friday;
while
循环语句是一种控制结构,它允许我们重复执行一段代码,只要给定的条件为真(非零),
while
循环的基本语法如下: 1
2
3
4while (condition) {
// 循环体
// 这里的代码会在条件为真时重复执行
}while
循环来累加从 1 到 100 的整数,并输出累加的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <iostream>
using namespace std;
int main() {
int sum = 0; // 初始化累加器
int i = 1; // 初始化计数器
while (i <= 100) {
sum += i; // 累加当前值
i++; // 更新计数器
}
cout << "Sum of numbers from 1 to 100 is: " << sum << endl;
return 0;
}
// Sum of numbers from 1 to 100 is: 5050do-while
循环是 C/C++
语言中的一种循环控制结构,它的特点是至少会执行一次循环体内的代码
,然后才检查循环条件。这与
while
循环不同,在 while
循环中,如果初始条件就不满足,则循环体内的代码不会被执行。
1 |
|
do-while
循环的工作流程:
1、执行循环体:首先执行循环体内的代码。
2、条件检查:执行完循环体后,检查 condition
是否为真。
3、重复步骤 1 和 2:如果 condition
为真,则再次执行循环体。这个过程会一直重复,直到 condition
变为假为止。
for循环语句
for
循环是 C/C++
语言中的一种循环控制结构,它允许你重复执行一段代码,通常用于已知循环次数的情况。for
循环提供了初始化
、条件检查
和迭代
三个部分在一个简洁的语法中。
基本语法: 1
2
3
4for (initialization; condition; iteration) {
// 循环体
// 这里的代码会在条件为真时重复执行
}
工作流程:
- 1、初始化:首先执行初始化部分。 -
2、条件检查:然后检查条件部分。如果条件为真,则执行循环体内的代码。 -
3、执行循环体:执行完循环体后,执行迭代部分。 - 4、重复步骤 2 和
3:再次检查条件部分,如果条件仍然为真,则再次执行循环体。这个过程会一直重复,直到条件变为假为止。
特殊用法:
- 1、空循环体:可以在 for
循环中使用空循环体,通常用于延时或等待操作。 -
2、省略部分:for
循环的初始化、条件和迭代部分都可以被省略,但分号 ;
必须保留。 - 3、多个变量:可以在初始化和迭代部分声明和更新多个变量。
循环中断语句
在 C++ 中,我们可以使用 break
和 continue
语句来中断循环。 - break
语句用于中断循环,即跳出循环。 -
continue
语句用于跳过当前循环的剩余部分,直接进入下一次循环。
1 |
|
1 |
|
goto语句
goto
语句是一种无条件跳转语句,它允许程序直接跳转到程序中的另一个指定位置。goto
语句在早期的编程实践中非常常见,但在现代编程风格中,它通常被认为是不良实践,因为它可能导致程序难以理解和维护。
1
2
3goto label;
label:
// 这里是跳转的目标位置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;
int main() {
int i = 0;
start:
cout << "Count: " << i << endl;
i++;
if (i < 5) {
goto start; // 当 i 小于 5 时,跳回到 start 标签
}
cout << "Loop ended." << endl;
return 0;
}
// Count: 0
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Loop ended.
输入信息控制循环
1 |
|
输出杨辉三角
数组
数组的特点: -
1、有序性:数组中的元素是有顺序的,可以通过索引来访问特定位置的元素 -
2、同类型:数组中的所有元素必须是相同的数据类型 -
3、连续存储:数组的元素在内存中是连续存储的,这使得随机访问非常快
1
2
3
4
5
6
7
8
9int numbers[10]; //定义一个包含10个整数的数组
numbers[0] = 10; // 设置第一个元素为10
int firstElement = numbers[0]; // 获取第一个元素
int numbers[] = {1, 2, 3, 4, 5}; // 定义并初始化数组
//计算数组的长度
int length = sizeof(numbers) / sizeof(numbers[0]);
多维数组
在C++中,多维数组是用来存储具有多个维度的数据结构。最常见的是二维数组,但也可以有三维或更高维度的数组。下面将详细介绍如何在C++中声明、初始化和使用多维数组
声明: 1
2
3
4
5// 声明一个3行4列的二维整型数组
int arr[3][4];
// 声明一个2x3x4的三维浮点型数组
float cube[2][3][4];
初始化: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 初始化一个3行4列的二维整型数组
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 完全初始化一个2x3x4的三维浮点型数组
float cube[2][3][4] = {
{{{1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0}, {9.0, 10.0, 11.0, 12.0}},
{{13.0, 14.0, 15.0, 16.0}, {17.0, 18.0, 19.0, 20.0}, {21.0, 22.0, 23.0, 24.0}}}
};
// 部分初始化
int arr2[3][4] = {
{1, 2, 3, 4}, // 第一行
{5, 6}, // 第二行,剩余位置自动填充0
{} // 第三行为空,所有位置自动填充0
};
访问: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问第一行第二列的元素
int element = arr[0][1]; // element 的值为 2
// 遍历整个数组
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
std::cout << "arr[" << i << "][" << j << "] = " << arr[i][j] << std::endl;
}
}
多维数组的内部布局:
多维数组在内存中是连续存储的,按照行优先(row-major
order)或列优先(column-major order)顺序存储。C++默认采用行优先顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 获取数组的地址
int (*p)[4] = arr; // p指向数组的第一行
// 获取某一行的地址
int *row = arr[1]; // row指向第二行的第一个元素
// 获取某个元素的地址
int *element = &arr[1][2]; // element指向第二行第三个元素
指针
- 指针基础概念:
- 定义:指针是一个变量,它的值是另一个变量的内存地址
- 声明:声明指针时,在变量类型后面加上星号
*
1
int *p; // 声明一个指向int类型的指针
- 初始化:
指针可以被初始化为
nullptr
或指向一个已存在的变量的地址1
2
3int x = 10;
int *p = &x; // p现在指向x的地址
int *q = nullptr; // q未指向任何地址 - 解引用:
使用星号*
来获取指针所指向的变量的值1
int value = *p; // value等于10
- 取地址:
使用&
运算符获取变量的地址1
int *addr = &x; // addr等于p
- 指针运算:
1
2
3
4int arr[5] = {0, 1, 2, 3, 4};
int *ptr = &arr[0];
ptr++; // ptr现在指向arr[1]
*ptr = 10; // arr[1]现在等于10 - 指针比较:
1
2
3
4
5
6int arr[5] = { 0, 1, 2, 3, 4 };
int* ptr = &arr[0];
ptr++; // ptr现在指向arr[1]
if (ptr == &arr[1]) {
std::cout << "ptr points to the same address as arr[1]" << std::endl;
} - 动态内存分配:
使用new
分配内存,使用delete
释放内存:1
2
3int *data = new int; // 分配一个int大小的空间
*data = 42; // 设置值
delete data; // 释放内存 - 数组的动态分配:
使用new[]
分配数组空间,使用delete[]
释放### 函数中的指针1
2
3int *array = new int[10];
array[0] = 1;
delete[] array; - 传入指针: 可以将指针作为参数传递给函数,从而改变原函数外部的数据。
1
2
3
4
5
6
7
8
9void setZero(int *p) {
*p = 0;
}
int main() {
int x = 5;
setZero(&x);
std::cout << x << std::endl; // 输出0
} - 返回指针: 函数可以返回一个指针 ### 指针与数组
1
2
3
4int *getPointer() {
static int val = 10;
return &val;
} - 数组名作为指针: 数组名实际上是一个指向数组第一个元素的常量指针
1
2int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // ptr指向arr[0] - 指针与数组的区别: 数组名不能被重新赋值,而指针可以 ### 指针与引用
1
2arr = nullptr; // 错误
ptr = nullptr; // 正确 - 引用: 引用不是指针,但它提供了一种别名的方式来访问同一变量
1
2
3int x = 10;
int &ref = x; // ref是x的引用
ref = 20; // x现在等于20 - 区别:
引用必须在声明时初始化,并且不能被改变;指针可以在任何时候被重新赋值
### 指针注意事项
1
2int *p = &x;
p = &ref; // 正确 - 空指针: 指针在未被正确初始化之前不应该被解引用。
- 野指针: 指向已释放内存的指针被称为野指针,使用野指针会导致未定义行为。
- 悬垂指针: 当指针指向的内存被释放后,该指针就成为悬垂指针。
练习题
1、 1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
using namespace std;
int main() {
int a, b, c;
a = b = c = 1;
++a ||++b && ++c;
cout << a;
cout << b;
cout << c;
return 0;
}
一、运算符优先级:
- && 的优先级高于 || - ++ 前缀递增运算符的优先级高于 &&
和 ||
二、短路求值: - ||
运算符遵循左短路原则:如果左边的表达式为真,则右边的表达式不会被计算。 -
&&
运算符遵循左短路原则:如果左边的表达式为假,则右边的表达式不会被计算。
1
2
3
4
5// 解析表达式:
++a || ++b && ++c 可以按照运算符优先级拆分为:
++a || (++b && ++c)
因为 ++a 会将 a 的值从 1 增加到 2,而 2 不等于 0(即 true),所以 ++a 的结果为 true
由于 || 运算符遵循左短路原则,++b && ++c 这部分不会被计算,因为 ++a 的结果已经是 true,无论 ++b && ++c 的结果如何,整个表达式的值都将为 true1
2
3
4
5
6
7
8#include <iostream>
using namespace std;
int main() {
cout << "c:\\tools\book.txt";
return 0;
}
// c:\toolook.txt