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
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
#include <iostream>  //对应c语言中的stdio.h---printf();
using namespace std; //std表示文件夹,封装好的代码

int main() //程序从main()函数开始、结束
{
//在操作系统眼中,我们的程序就是一个函数,即:main()函数
// if__name__ = __ main__ 表示判断是否直接运行含有main()函数的程序,还是引用其他程序

//写的每一个常量都是有类型的!
cout << "2: " << typeid(2).name() << endl;
cout << "2l: " << typeid(2l).name() << endl;
cout << "2.0f: " << typeid(2.0f).name() << endl;
cout << "2.0: " << typeid(2.0).name() << endl;//注意2.0是double,默认是双精度

//字符常量赋值给字符类型
char a = 'x';
//注意不能写成 char a = "x"---单字符char不能用双引号赋值
//双引号"x"表示一个字符串,'x'表示一个字符
//字符串用 \0 结束
cout << "a: " << a << endl;

//科学计数法
cout << ".145: "<<.145 << endl;
cout << "2.145E-1: " << 2.145E-1 << endl;
cout << "2.145e2: " << 2.145e2 << endl;

//把b定义为常量,不能修改
const int b = 1;
// b = 2; 会报错

return 0;
}
1
2
3
4
5
6
//变量
int a = 1;
//1、变量类型:int
//2、变量名:a
//3、变量地址:内存中的地址
//4、变量值: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
#include <iostream>  
using namespace std;

int main()
{
int a = 3.6;
float b = 5 / 2;
int c = 5 % 2;
float d = 5 / 2.0; //类型会向上转换
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "d = " << d << endl;

int e = 1;
int f = (++e) * 2;
cout << "e = " << e << endl;
cout << "f = " << f << endl;

int g = 1;
int h = (g++) * 2;
cout << "g = " << g << endl;
cout << "h = " << h << endl;

}

条件表达式

在计算机中,0 代表假,非 0 代表真

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>  
using namespace std;

int main()
{
//0代表假,非0代表真
int x = 1;
int y = 2;
int z = 3;
//cout << (x = y) << endl; //这是一个赋值的表达式,表示 x 等于 2,注意这里会对后续 x 的值产生影响
cout << (x == y) << endl;
cout << ('a' == 97) << endl;
cout << "a=" << x++ + y << endl; //表示x++ +y
//2
//0
//1
//a = 3
}

if 判断语句

这里就不记代码了

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>  
using namespace std;

int main()
{
int a = 0;
for (int i = 0;i < 3;i++)
{
a += i;
}
cout << "a = " << a << endl;

}
// a = 3

for 循环与 while 循环类似,可以相互转换

这里就不记代码了,偷点懒...

循环嵌套

略...

break 与 continue

break 表示结束当前循环,continue 表示跳过本次循环

switch 与 case

选择语句执行,需要配合break食用

第二讲实验记录

第一题写的不好,一开始看错题了,回去好好改一下

请注意,本题的目标是对原始的字符数组进行修正!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Delete all lowercase ‘d’ from a string, e.g. input the string “abcdabcd” and output “abcabc”. Note that the string is a C string, which means it is an array of characters ending with ‘\0’.

#include <iostream>
using namespace std;

int main()
{
char str[] = "abcdabcd";
int len = strlen(str);
int i = 0, j = 0;
while (i < len) {
if (str[i] != 'd') {
str[j++] = str[i];
}
i++;
if (i == len - 1 && str[i] == 'd')
str[i] = '\0';
}
str[j] = '\0';
for (int i = 0;i < len;i++)
cout << str[i];
}

这里优化了一下:

但是实际上这里还有有问题,对于原始数组还是没有进行完全的修改(\0后面还有字符,只是没有输出出来!!!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
char str[] = "abcdabcdd";
int len = strlen(str);
int i = 0, j = 0;
while (i < len) {
if (str[i] != 'd') {
str[j++] = str[i];
}
i++;
}
str[j] = '\0';
int t = 0;
while (str[t] != '\0') //因为考虑到字符数组中间也有\0这一字符,所以选择遇到第一个\0时跳出循环,停止输出
{
cout << str[t];
t += 1;
}
}
//注意上面的解法是不对的...

最终的版本:这下应该是比较合理的了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
char str[] = "abcdddabcd";
int i = 0, j = 0;
while (i < strlen(str)) {
if (str[i] != 'd') {
str[j++] = str[i];
}
i++;
}
str[j] = '\0';
for (int i = 0;i < strlen(str);i++) //因为strlen只能读取到第一个\0为止,因此在上面的版本基础上进一步进行了优化
cout << str[i];
}

第二题:本题比较简单,没有什么坑,只需要遍历一遍数组,然后根据比较的结果进行替换即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Find the second largest value in an array. For example, if you have the array {1, 4, 2, 3, 5}, which returns 4.

int main()
{
const int n = 5;
int a[n];
int first = 0;
int second = 0;

for(int i=0;i<n;i++)
{
cin >> a[i];
if (a[i] > second && a[i] < first)
{
second = a[i];
}
if (a[i] > first)
{
second = first;
first = a[i];
}
}
cout << "the secode largest num is : " << second << endl;
}

第三问感觉写的也有点问题,有点局限了,没有考虑特殊情况

如果这个数组中出现了两个出现次数为奇数的情况怎么办???

这种情况下就不能简答的使用异或操作了,因为两个奇数异或的结果是0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// There are N positive integers in an array. Only one of them appears an odd number of times. The others appear an even number of times. Find the number that appears an odd number of times and output it. For example, input an array {1, 2, 4, 3, 3, 1, 3, 4, 2}, then output 3. 

int main()
{
const int N = 9;
int a[N];
int result = 0;
for (int i = 0;i < N;i++)
{
cin >> a[i];
result ^= a[i];
}
cout << "the odd num is : "<<result << endl;
}

第三讲

array

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;

int main()
{
int a = 1;
int* p1 = &a;//p1也算一个变量,特点也一样
//类型是int* 名字是p1,值是变量a的地址

//&a表示取地址:取变量的地址
//*p1表示取内容:取当前地址保存的内容
//int*不是一个操作符,是一个类型
//a和*p1都表示一个房子,基本没有区别
cout << p1 << endl;
cout << &a << endl;
cout << *p1 << endl;

cout << &(*p1) << endl; //等价于a的地址
cout << *(&a) << endl; //等级于a

return 0;
}
// 0000006D754FF8A4
// 0000006D754FF8A4
// 1
// 0000006D754FF8A4
// 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
//数组的基本用法---一维
#include <iostream>
using namespace std;

int main()
{
int a[5] = { 0 };
int b(5);//定义一个变量,初始化为5

cout << a << endl; //打印数组第一个元素的地址
cout << a[0] << endl; //打印第一个元素的值
cout << b << endl; //打印 5

for (int i = 0;i < 5;++i)
{
cout << a[i] << endl; //打印5个0
}

cout << "array size: " << sizeof(a) << endl;
cout << "array element size: " << sizeof(a[0]) << endl;
cout << "number of elements: " << sizeof(a) / sizeof(a[0]) << endl;

cout << a[5] << endl; //等价于下面的
cout << *(a + 5) << endl; //表示地址可以直接相加
//因为操作系统会预留一部分内存空间,所以超出一点不会报错,但是读取会出现问题

return 0;
}
// 000000EF0CAFF9E8
// 0
// 5
// 0
// 0
// 0
// 0
// 0
// array size: 20
// array element size: 4
// number of elements: 5
// -858993460
// -858993460
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
//二维数组:在内存中不存在二维数组,还是用一维数组来表示二维的概念,示例如下:
#include <iostream>
using namespace std;

int main()
{
int a[6] = { 1,2,3,4,5,6 };
int b[3][2] = { 1,2,3,4,5,6 };

for (int i = 0;i < 3;i++)
{
for (int j = 0;j < 2;j++)
{
cout << a[i * 2 + j];
}
}
cout << endl;

//同下:
for (int i = 0;i < 3;i++)
{
for (int j = 0;j < 2;j++)
{
cout << b[i][j];
}
}
cout<< endl;

return 0;
}
// 123456
// 123456
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;

int main()
{
char a[] = "jinan";
char b[] = "jinan";

if (a == b) { // == 比较的是存储的具体内容是否一样
cout << "true" << endl;
}
else {
cout << "false" << endl;
}
//a b 表示的其实是一个指针---比较的是数组的地址

string c = "jinan"; //string 运算符重载 可以解决上述问题
string d = "jinan";

if (c == d) {
cout << "true" << endl;
}
else {
cout << "false" << endl;
}

return 0;
}
// false
// true

char[] 的比较:a == b 比较的是数组的地址,而不是数组的内容

string 的比较:c == d 比较的是字符串的内容,因为 string 类重载了 == 运算符

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>  
using namespace std;

int main()
{
//char a[5] = "jinan"; //这里会报错,字符串数组必须以\0结束
char b[6] = "jinan";
cout << b << endl;

return 0;
}
//jinan

第四讲

Function

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;

void fun1()
{
cout << "Hello!" << endl;
}

void fun2(int a)
{
cout << "a = " << a << endl;
}

char fun3()
{
return 'X';
}

float fun4(int a)
{
return a * a;
}

int main()
{
fun1();
fun2(2);

char c = fun3();
cout << c << endl;

float f = fun4(2.0);
cout << "f = " << f << endl;

return 0;
}
// Hello!
// a = 2
// X
// f = 4
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;

int a = 1;
int b = 2;
extern int c;

void fun(int a)
{
cout << "fun():a = " << a << endl;
cout << "fun():b = " << b << endl;
cout << "fun():c = " << c << endl;
}

int main()
{
int a = 3;
cout << "main():a = " << a << endl;

fun(4);

int i = 0;
for (;i < 1;i++)
{
cout << "for():a = " << a << endl;
}
cout << "main():i = " << i << endl;

return 0;
}

int c = 5;
// main():a = 3
// fun():a = 4
// fun():b = 2
// fun():c = 5
// for():a = 3
// main():i = 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
2
3
//区别
#include <iostream> //系统的
#include "test.h" //自己写的

指针基础 ⭐⭐⭐⭐⭐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main()
{
int a = 1;
int* p1 = &a;

int* p2 = NULL; //NULL表示0
p2 = p1;
int** p3 = &p2;
// **P3与a和*p2等价

cout << p2 << endl; //p2存的值是a的地址
cout << &p2 << endl; //表示p2的地址
cout << p3 << endl; //p3表示p2的地址
cout << *p3 << endl; //p3取值是p2存的值 就是a的地址
}
// 1、4是相同的,2、3是相同的输出
// 0000003DE04FF994
// 0000003DE04FF9D8
// 0000003DE04FF9D8
// 0000003DE04FF994
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>  
using namespace std;

void main()
{
int b[] = { 1,2,3,4,5 };
int* p4 = b;
cout << b << endl;
cout << b[2] << endl;
cout << *(b + 2) << endl;
cout << *(++p4) << endl;
cout << *(p4++) << endl;
cout << *(p4 - 2) << endl;
}
// 输出如下:
// 0000002829EFFAA8
// 3
// 3
// 2
// 2
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>  
using namespace std;

void main()
{
int a = 1, b = 2;
int c[] = { 1,2 };

int* x = &a, * y = &b;
int t = *x;
*x = *y;
*y = t;
cout << *x << *y << endl;
cout << a << b << endl; //ab会受影响
}
// 21
// 21
// 通过指针操作实现了两个整数变量a和b的值交换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>  
using namespace std;

void swap(int x, int y)
{
int t = x;
x = y;
y = t;
cout << "(x,y) = (" << x << "," << y << ")" << endl;
}
void main()
{
int a = 1, b = 2;
int c[] = { 1,2 };

swap(a, b);
cout << "(a,b) = ("<<a <<"," << b<<")" << endl;
}
// (x,y) = (2,1)
// (a,b) = (1,2)
// 这种情况下ab不变

第三讲实验记录

第三次实验课只有一道题,但是这题感觉难度比较大,因为老师的要求比较高,一开始使用的第二种方法,需要两次遍历加上循环输出,老师认为还有更好的方法,所以尝试使用了第一种,只需要一次遍历,但是使用到了两个指针,类似快慢指针的解法,老师认为还可以优化,后面就没有好的想法了,后面复习的话需要认真看看这道题!

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
#include <iostream>
using namespace std;

int main() { //解法一
int N;
cin >> N;
bool found = false;
int left = 1, right = 1, sum = 1;
while (right < N/2) {
if (sum < N) {
right++;
sum += right;
}
else if (sum > N) {
sum -= left;
left++;
}
else {
found = true;
cout << N << "=";
for (int i = left; i < right; i++) {
cout << i << "+";
}
cout << right << endl;
sum -= left;
left++;
}
}
if (!found)
cout << "not found" << endl;
return 0;
}

int main() //解法二
{
int n;
cin >> n;
cout << "n=" << n << endl;
bool find = false;
for (int i = 1;i < n-1;i++)
{
int j = i + 1;
for (;j < n;j++)
{
int sum =(i + j) * (j - i + 1) / 2;
if (sum == n)
{
find = true;
cout << "100=";
for (int k = i;k < j;k++)
{
cout << k << "+";
}
cout << j << endl;
}
}
}
if (find == false)
{
cout << "not found" << endl;
}
return 0;
}

第四讲实验记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 第一题使用递归设计一个斐波那契数列即可
#include <iostream>
#include <vector>
using namespace std;

int Fibonacci(int N)
{
if (N == 1) return 0;
else if (N == 2) return 1;
else {
return Fibonacci(N - 1) + Fibonacci(N - 2);
}
}

void main()
{
int N = 5;
cout << Fibonacci(N) << 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
55
56
57
58
59
60
61
62
63
64
65
66
67
// 第二题需要对有序数组进行插入操作,使用到了二分遍历找到插入点,然后从后往前进行插入,提供了vector版本

int binarySearch(int arr[], int size, int num) {
int left = 0;
int right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == num) {
return mid;
}
else if (arr[mid] < num) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return left;
}

int binarySearch(const std::vector<int>& arr, int num) //使用vector来实现
{
int left = 0;
int right = arr.size() - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] < num) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return left;
}


void main()
{
// 静态二分法实现
int arr[] = { 1, 2, 3, 5, 7, 9 };
int size = sizeof(arr) / sizeof(arr[0]);
int num = 12;

int insertPoint = binarySearch(arr, size, num);
for (int j = size; j > insertPoint; j--) {
arr[j] = arr[j - 1];
}
arr[insertPoint] = num;

for (int i = 0;i < size + 1;i++)
{
cout << arr[i] << endl;
}

//answer2 vector实现
std::vector<int>arr = { 1,2,3,5,7,9 };
int num = 12;
int insertPoint = binarySearch(arr, num); // 找到插入点
arr.insert(arr.begin() + insertPoint, num); //插入

for (const auto& element : arr)
{
std::cout << element <<std::endl;
}
}
1
2
3
4
5
6
7
8
9
10
11
// 第三题是个数学题
void main()
{
int arr[] = { 3,6,4,5,9,8,1,2,0};
int sum = 0;
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
{
sum += arr[i];
}
cout << ((0+9) * 10) / 2 - sum << 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
#include <iostream>  
using namespace std;

void swap(int x, int y) //函数的重载
{
int t = x;
x = y;
y = t;
cout << "(x,y) = (" << x << "," << y << ")" << endl;
}

void swap(int* x, int* y)
{
int t = *x;
*x = *y;
*y = t;
cout << "(x,y) = (" << *x << "," << *y << ")" << endl;
}

void swap(int c[]) //c和cpp中没有传数组这一说,其实传递的是指针 *c
{
int t = c[0]; //c[0] = *(c+0) = *c 语法糖
c[0] = c[1]; //c[1] = *(c+1) 指针后移
c[1] = t;
cout << "(c[0],c[1]) = (" << c[0] << "," << c[1] << ")" << endl;
cout << "size(c) = " << sizeof(c) << endl; //为了证明传入的c是一个指针,在32位系统中,指针是4字节,32位
}

void main()
{
int a = 1, b = 2;
int c[] = { 1,2 };
swap(a, b);
cout << "(a,b) = (" << a << "," << b << ")" << endl;
//(x, y) = (2, 1)
//(a, b) = (1, 2)

swap(&a, &b);
cout << "(a,b) = (" << a << "," << b << ")" << endl;
//(x, y) = (2, 1)
//(a, b) = (2, 1)

swap(c);
cout << "(c[0],c[1]) = (" << c[0] << "," << c[1] << ")" << endl;
cout << "size(c) = " << sizeof(c) << endl; //这里的c是一个数组,2个int,大小共8字节
//(c[0], c[1]) = (2, 1)
//size(c) = 4
//(c[0], c[1]) = (2, 1)
//size(c) = 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
#include<iostream>
using namespace std;

void main()
{
int a[3] = { 1,2,3 };
int* b = a;
int c = 0;

//依次执行...
c = *(b + 1);
// a[0],a[1],a[2] = 1,2,3
// *b = 1
// c = 2
c = *b + 1;
// a[0],a[1],a[2] = 1,2,3
// *b = 1
// c = 2
c = *(++b);
// a[0],a[1],a[2] = 1,2,3
// *b = 2
// c = 2
c = *(b++);
// a[0],a[1],a[2] = 1,2,3
// *b = 2
// c = 1
c = ++(*b);
// 这个判断错了!!!
// a[0],a[1],a[2] = 2,2,3
// *b = 2
// c = 2
c = (*b)++;
// 这个判断错了!!!
// a[0],a[1],a[2] = 2,2,3
// *b = 2
// c = 1

cout << "a[0],a[1],a[2] = " << a[0] <<"," << a[1]<<"," << a[2] << endl;
cout << "*b = " << *b << endl;
cout << "c = " << c << 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
int 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中,使用newdelete来实现堆空间的分配和释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main()
{
int a = int(1); // 创建一个int类型的变量,变量a的值是1 ,可以理解为一个函数 int --- 构造函数
int* p1 = new int(1); // 分配了1个int大小的内存,返回的是首地址,存的值是1
int* p2 = new int[3]; // 分配了3个int大小的内存,返回的是首地址,表示一个数组,未赋值
// 注意:堆空间创建时,少了变量名这一属性
// 堆空间的变量没有名字,只能通过 *p1 *p2 指针来访问
// 创建的指针 *p1 *p2 在栈空间,指向的int在堆空间

delete p1; // 释放p1指向的内存,删除一个堆块
delete[] p2; // 释放p2指向的内存,删除数组

// 当删除p1时,实际上删除的只是p1指向的堆空间,p1指针本身没有被删除,所以可以再次使用p1
p1 = new int(2); // 释放了p1指向的内存,重新分配了内存,p1指向新的内存
cout << "p1 =" << *p1 << endl; // p1 = 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int fun1()
{
int a = 10;
int b = a;
return b;
}

int fun2()
{
int a = 10;
int* p = &a;
return p;
}

int fun3() //fun3与fun2写法是一样的
{
int a = int(10);
int* p = &a;
return p;
}

int fun4()
{
int* p = new int(10); //创建的是堆空间,不会自动释放,需要手动释放
return *p;
}

void main()
{
//问题一
int a = fun1();
cout << "a=" << a << endl; // a=10

//问题二
int* p = fun2();
cout << "p=" << *p << endl; // 答案是不知道
// fun2的栈空间已经结束,p指向了fun2的栈空间,所以p的值是不确定,但是,在release模式下,p的值是10

//问题三
int* p = fun4();
cout << "p=" << *p << endl; // p=10
}

memory leak

内存泄漏是指程序在申请内存后,无法释放已分配的内存空间,导致程序运行过程中可用内存逐渐减少的现象。长时间运行或大量数据处理的应用程序特别容易出现内存泄漏问题。

引用数据类型

1
2
3
4
// ① 定义 普通 类型 变量
int a = 8;
// ② 定义 引用类型变量, 格式 : 类型名称& 变量名 = 对应类型变量名称 ;
int& b = a;

int& 是引用数据类型 , b 是 a 的引用;

引用数据类型的本质是:给变量 a 所在内存赋予另外一个别名 b

引用数据类型的使用方法 : 直接当做原来的变量使用即可, 可以替换原来变量的位置使用

1
2
3
4
5
6
7
8
// 1. 修改引用类型变量值 , 引用类型做参数 , 修改引用值
void quote(int& b) {
//修改引用类型变量值
b = 888;
}

// 2. 打印引用数据类型的修改结果 , 结果是 b 被修改成了 888
cout << b << 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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//定义一个结构体
struct A{
char a; //成员变量 或者属性,1字节
int y; // 4字节
short z; // 2字节
};

//为什么需要结构体,因为基本数据类型不够用

struct B{
char a;
short b;
int c;
};

void main()
{
struct A a; //创建一个A类型的变量a,a是一个创建的对象
a.a = 'a'; // 对 对象a的属性进行赋值
a.y = 1;
a.z = 2;
}

内存对齐的概念:

结构体中的每一个属性在内存中需要实现内存对齐,即按照最长的字节长度进行填补

在上面的例子中,对象 a 的内存是12个字节

但是用上面结构体 B 创建的对象,只需要占用8字节内存,节约了内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Teacher{
string name;
int age;
char gender;
};

void main()
{
struct Teacher t;
t.name = "zhangsan";
t.age = 35;
t.gender = 'M';
cout<<t.name<<endl;
cout<<t.age<<endl;
cout<<t.gender<<endl;
cout<<sizeof(t)<<endl; // 输出为28,这里好像是不随name的长度变化的
}

对于结构体的定义已经对象的创建可以简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef 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
20
typedef 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
25
typedef 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

union info
{
char gender; //1字节
int age; //4字节
double score; //8字节
};

void main()
{
info stu;
cout<<sizeof(stu)<<endl; // 一共只有8字节,属性里面最大的那个,同时只能有一个起作用
stu.age = 18;
cout<<stu.age<<endl;
stu.gender = 'm';
cout<<stu.gender<<endl;
stu.score = 99.9;
cout<<stu.score<<endl;
cout<<stu.age<<endl; // 这里无法打印age属性
}

第七讲 面向对象编程

类与对象 ⭐⭐⭐⭐⭐

定义一个类,类除了有属性,还有行为(函数)

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
class 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
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
class Student{
public:
int id;

// 三种构造函数
// 构造函数的名字必须与类的名字相同!!!

// 第一种,无参构造函数/默认构造函数
Student(){
id=0;//给属性一个默认值
cout<<"Student()"<<endl;//这个输出用来表示调用了该函数
}

// 第二种,带参构造函数
Student(int i){
id=i;
cout<<"Student(int i)"<<endl;
}

// 第三种,复制构造函数,也叫拷贝构造函数
Student(const Student &s){
id=s.id;
cout<<"Student(const Student &s)"<<endl;
}
};

void fun1(Student s)
{
}

Student fun2()
{
Student s1 = Student(1); //函数体里调用有参构造
return s1;
}

void main()
{
// 下面三种方法都可以实现:创建对象并使用无参构造函数
Student s1; // 创建对象时会自动调用无参构造函数
Student s2 = Student(); // 逻辑同上一行,创建对象,调用无参构造函数i
Student s3 = {}; // 逻辑同上
cout<<"s1.id="<<s1.id<<endl;
cout<<"s2.id="<<s2.id<<endl;
cout<<"s3.id="<<s3.id<<endl;

// 下面三种方法都可以实现:创建对象并使用有参构造函数
Student s4(1);
Student s5 = Student(1);//第二种方式更优
Student s6 = {1};
cout<<"s4.id="<<s4.id<<endl;
cout<<"s5.id="<<s5.id<<endl;
cout<<"s6.id="<<s6.id<<endl;

// 下面四种方法都可以实现拷贝构造函数的调用
Student s1;

Student s7(s1);
Student s8 = Student(s1);
Student s9 = {s1};
Student s10 = s1; //也是调用了拷贝构造函数,等价于: Student s10 = Student(s1) = Student s1;

// s1 s2 s4 s5 s7 s8 s10 必须要记住!!!

// 容易混淆的点
Student s1; // 调用一次无参构造函数
Student s2; // 调用一次无参构造函数
s2 = s1; // 只是执行了赋值的操作,注意没有调用拷贝构造函数

// 容易混淆的点
Student(s1) = Student s1; // 两者之间是等价的关系!!!但是前者比较怪,一般不用

fun1(Student(s1)) //这里调用的是拷贝构造函数,需要注意!!!

Student s2 = fun2(); //mian空间调用了拷贝构造函数
//这里逻辑同下
Student s2; //无参构造
s2 = fun2(); // main空间调用了拷贝构造函数,因为对于fun2的返回值来说,需要在main空间调用拷贝构造开辟空间,然后复制给s2;函数空间调用了有参构造
}

一共有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
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
#include <iostream>
using namespace std;

class Student {
public:
int id;

Student() {
id = 1;
}
};

class Teacher {
public:
int* id;

Teacher() {
id = new int(2);
}
};

void main() {
Student s1;
Student* s2 = &s1; //s2是个指针,等于s1的地址,s2字节数与操作系统位数有关
Student* s3 = new Student(); //s3是堆空间的一个指针,调用的是无参构造函数
Student& s4 = s1; //s4是个引用,不会调用任何函数

Student s5[3] = { Student(), Student(), Student() };//s5是一个类数组
Student* s6[3] = { new Student(), new Student(), new Student() }; //指针数组,数组里的每个元素都是指针

Teacher* t3 = new Teacher(); //与s3不同点是,t3指向堆空间,类内部是也指针,也指向堆空间
}

析构函数 ⭐⭐⭐⭐⭐

析构函数是一个成员函数,在对象超出范围或通过调用 delete 或 delete[] 显式销毁对象时,会自动调用析构函数。 析构函数与类同名,前面带有波形符 (~)

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
#include <iostream>
using namespace std;

class Student {
public:
int id;

Student() {
id = 0;
//id = int(0);
cout << "Student()" << endl;
}

~Student() { //析构函数是一个成员函数,在对象超出范围或通过调用 delete 或 delete[] 显式销毁对象时,会自动调用析构函数。 析构函数与类同名,前面带有波形符 (~)
cout << "~Student()" << endl;
}
};

class Teacher {
public:
int* age;

Teacher() {
age = new int(0);//指向堆空间的变量
cout << "Teacher()" << endl;
}

~Teacher() { //为了删除对象创建的函数,析构函数---先删除属性,再回收内存
delete age; // 释放堆空间 防止内存泄漏,删除age指针指向的堆内存,先删内存再删房子
cout << "~Teacher()" << endl;
}
};

int* test1() {//用来显示析构函数
Student s = Student();
return &s.id;//返回值是id的地址,但是id是局部变量,会超出范围,返回的地址是无效的
}

int* test2() {
Teacher t = Teacher();
return t.age;
}

void test3() {
Teacher t = Teacher();
}

void main() {
Student s = Student(); //调用构造函数创建对象

int* id = test1();
cout << *id << endl;//无法实现取内容,因为输出的结果是未知,但是会调用构造函数和析构函数

int* age = test2();// age = t.age age指针指向堆空间
cout << *age << endl;//可以实现取内容,值为0,也会调用构造函数和析构函数

while (1) {//在堆空间会造成内存泄漏!!!
test3();
}

system("pause");
//在这之后调用析构函数
//因为s是main栈空间的临时变量,所以会在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
51
52
53
54
55
56
57
#include <iostream>
using namespace std;

class Student {
public:
int id;

Student() {
id = 0;
cout << "Student()" << endl;
}

~Student() {
cout << "~Student()" << endl;
}
};

class Teacher {
public:
int* age;

Teacher() {
age = new int(0); //
cout << "Teacher()" << endl;
}

~Teacher() {
delete age;
cout << "~Teacher()" << endl;
}

Teacher(const Teacher& t) { // 自己写一个拷贝函数,来实现深拷贝
age = new int(*t.age); // 深拷贝的话会自己再堆空间创建新的变量,而不是两个对象共享一个堆空间的变量
cout << "Teacher(const Teacher & s)" << endl;
}
};

void test1() {
Student s1;
Student s2(s1); // 浅拷贝

cout << s1.id << endl;
cout << s2.id << endl;
}

void test2() { // 在堆空间创建对象
Teacher t1;
Teacher t2(t1); // 还是浅拷贝

cout << *t1.age << endl;
cout << *t2.age << endl; //程序结束时会重复删除一个堆空间的变量,出现问题
}

void main() {
test1();
//test2();
}

使用深拷贝还是浅拷贝,需要根据实际情况来决定!!!

初始化列表 ⭐⭐⭐

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
#include <iostream>
using namespace std;

class Student {
public:
int id;

Student() : id(0) { // 初始化id属性
cout << "Student() : id(0)" << endl;
}

~Student() {
cout << "~Student()" << endl;
}

Student(int _id) : id(_id) {
cout << "Student(int _id) : id(_id)" << endl;
}

Student(const Student& s) : id(s.id) {
cout << "Student(const Student& s) : id(s.id)" << endl;
}

Student& operator = (const Student& s) {
id = s.id;
cout << "operator = (const Student& s)" << endl;

return *this;
}
};

class Teacher1 {
public:
Student s;

Teacher1(Student& _s) { // 这个是有参构造函数
s = _s; // 如果不使用初始化列表,会先调用stu的无参构造函数,初始化一个stu,然后再调用Teacher的有参构造函数,会多一步
cout << "Teacher1(Student& _s)" << endl;
}

~Teacher1() {
cout << "~Teacher1()" << endl;
}
};

class Teacher2 {
public:
Student s;

Teacher2(Student& _s) : s(_s) { // 使用初始化列表来实现有参构造
cout << "Teacher2(Student& _s): s(_s)" << endl; // 使用初始化列表,会先调用stu的拷贝构造函数,再调用Teacher的有参构造函数
}

~Teacher2() {
cout << "~Teacher2()" << endl;
}
};

void test() {
Student s;

Teacher1 t1(s);
//Teacher2 t2(s);
}

void main() {
test();
}

初始化列表可以提高效率,原理不是很重要

this指针 与 连锁调用 ⭐⭐⭐⭐⭐

this最大的作用,将当前对象与类中的对应调用的函数联系起来,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
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;

class Student {
public:
int id;

Student(int id) {
// id = id; // 这里如果不用this指针会冲突,指代不明
this->id = id; //this指针指向调用这个函数的对象
}

Student(const Student& s) { // 隐藏的内容:Student(const Student& s, Student* this) {
id = s.id;
cout << "Student(const Student& s)" << endl;
}

Student& id_plus1(Student& s) { // 返回值是Student&,所以可以链式调用
id += s.id;
return *this;
}

Student* id_plus2(Student& s) { // 返回值是Student*,所以可以链式调用
id += s.id;
return this;
}

Student id_plus3(Student& s) { // 返回值是Student,所以不能链式调用
id += s.id;
return *this; // 返回时会创建一个临时变量Student,不能链式调用
}
};

void main() {
Student s1(18); // 当前调用有参构造函数的对象是s1,因此上面对应函数中的this指针指向s1 完整的内容应该为:Student s1(18,&s1);
cout << s1.id << endl;

Student s2(18);
s2.id_plus1(s1).id_plus1(s1); // 可以连锁调用 结果为:54
s2.id_plus2(s1)->id_plus2(s1); // 可以连锁调用 结果为:54
s2.id_plus3(s1).id_plus3(s1); // 不能连锁调用 结果为:36
//对于第三种情况的解读:
// Student T = *this T.id_plus3(s1) 第二个s1的id加到了这个临时变量T中,而不是s2中

cout << s2.id << endl;
}

第九讲

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
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
using namespace std;

class Student {
public:
int id;
mutable int age; // mutable关键字,表示这个变量是可变的,可以突破const的限制,可以修改
const int hands; //Cannot modify this variable
//const int hands = 2;

Student() : hands(2) { // 因为是const类型的hands,不能赋值,所以使用初始化列表的方式实现
id = 1;
age = 100;
}

//No modification in the function
void modify1() const { // const 函数中不能修改任何东西,包括const类型的变量
id = 100;
age = 18;
}

void modify2() {
id = 100;
age = 18;
}
};

void main() {
int a = 1;

const int b = 1; // 表示这个常量不能修改
b = 2;

const int* c = &a; // 表示指针指向的内容不能修改,但是指针指向的地址可以修改
*c = 2;
c = &b;

int* const d = &a; // 表示指针指向的地方不能修改,但是指向的内容可以修改
d = &b;
*d = 2;

const int* const e = &a; // 都不能修改

const int& f = a; // 引用类型本来就做为别名,不能在指向别的地方,所以这里实现的是指向的内容不能修改
f = 2;

const Student s;
s.id = 100;
s.age = 18;

s.modify1();
s.modify2();
}

static关键字原理 ⭐⭐

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
class Student {
public:
char color;
static int eyes;

Student():color('b') {}

void modify() {
color = 'w';
eyes = 100; // 如果修改静态属性,所有的实例都会跟着改
}

static void revise() // 静态成员函数是共享的
{
color = 'r'; // 静态方法不能修改实例属性,只能修改静态属性,原理上是因为静态方法没有this指针!!!
eyes = 10;
}
};

int Student::eyes = 2; // 静态成员变量的初始化在外面实现!!!

void main()
{
Student s1;
Student s2;

s1.eyes = 3;

Student::revise(); // 可以通过类别来调用函数,注意没有传入当前对象,不需要实例
// 静态函数可以修改静态属性,但是不能修改实例属性
s1.revise(); // 也是只能修改静态属性

s.modify(); // 可以利用一个实例对动态属性以及静态属性进行修改
}
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
#include <iostream>
using namespace std;

class Student {
public:
char color;
static int n_eyes;

Student() : color('b') {}

void modify() {
color = 'w';
n_eyes = 100;
}

static void revise() {
//color = 'r'; // 不能修改实例属性,没有传入this指针
n_eyes = 10; // 静态函数可以修改静态属性,但是不能修改实例属性
}
};

int Student::n_eyes = 2;

void main() {
Student s;
Student s1;

Student::n_eyes = 5; // 通过类修改静态属性
s1.n_eyes = 5; // 通过一个实例对象来修改静态属性

Student::revise(); // 一个静态的函数,所有对象共用一个函数
s1.revise();

s.modify();

cout << Student::n_eyes << endl;
cout << s.color << endl;
cout << s.n_eyes << endl;
cout << s1.color << endl;
cout << s1.n_eyes << endl;
}

分析对象的大小

总结:

  • 当一个对象没有任何属性和方法时,占用内存 1 字节
  • 类中的static属性不占用任何空间,static属性单独存储
  • 类中的任何函数(动态函数和静态函数)都不占用类的空间存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

class Student {
public:
int id; // 普通属性跟着实例走
static int age; // static属性不影响对象大小,不跟实例走

void fun1() {} // 增加一个函数对象的大小不变,在调用函数时利用传入的this指针来绑定
static void fun2(){}// 同样不增加对象的大小
};

int Student::age = 18;

void main() {
Student s; // 当没有任何属性或者方法时,内存大小为1字节来占位
// 当加入int属性后对象4字节
// 当加入static属性后,对象还是4字节,因为static属性不参与实例的创建
// 函数以及static函数都不影响实例对象的大小
cout << sizeof(s) << endl;
}

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
using namespace std;

class Teacher;
class Student {
private:
int age; //私有属性,其他对象无法访问
friend void good_friend(Student& s); //1、申明外面的某个函数为friend
friend class Teacher;//2、申明Teacher类为friend
public:
int id;

Student() : age(18), id(0) {}
void ask(Teacher& t);
};

class Teacher {
int age; // 不写也是默认为private属性
friend void Student::ask(Teacher& t);//3、申明某个类别里面的成员函数为friend
public:
int id;

Teacher() : age(18), id(0) {}

void ask(Student& s) {
cout << s.id << endl;
cout << s.age << endl;
}
};

void Student::ask(Teacher& t) { // student的函数ask可以在外面定义,为了防止类别定义的嵌套问题,类别里面申明,外面定义会比较好
cout << t.id << endl;
cout << t.age << endl;
}

void good_friend(Student& s) {
cout << s.id << endl;
cout << s.age << endl;
}

void main() {
Student s;
Teacher t;

good_friend(s);
t.ask(s);
s.ask(t);
}

第十讲

面向对象编程的第二特性---继承(inheritance)

面向对象编程的三大特性:封装、继承、多态

特性 核心思想 优点
封装 隐藏内部实现,暴露必要接口 提高安全性,降低耦合性
继承 子类继承父类的属性和方法,支持代码复用和扩展 提高代码复用性,支持层次化设计
多态 同一接口在不同对象中有不同实现 提高灵活性和可扩展性,支持面向接口编程

继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在子类中添加新的属性和方法,或者重写父类的方法,提高代码的复用性

子类继承父类,会继承父类的所有成员变量和成员函数,同时子类也可以添加新的属性和方法,或者重写父类的方法

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
#include <iostream>
#include <string>
using namespace std;

class Student { //学生
public:
string name;
char gender;
int age;
string major;
};

class Teacher { //老师
public:
string name;
char gender;
int age;
string title;
};

class Programmer { //程序员
public:
string name;
char gender;
int age;
int hair;
};
// 三个类别都有特性和共性,共性可以继承实现
// 三个类都继承:人

// 子类继承父类,“人”是父类(基类,baseclass)
// 子类也叫派生类

// 下面是使用继承的方式来实现上面一样的功能
class Person {
public:
string name;
char gender;
int age;
};

class Student : public Person { //public Person 表示使用共有继承(所有属性和方法)
public:
string major; //只需要定义私有属性,其他属性都从父类继承下来
};

class Teacher : public Person {
public:
string title;
};

class Programmer : public Person {
public:
int hair;
};

void main() {
Student s;
s.major = "CS";
Teacher t;
t.title = "Prof.";
Programmer p;
p.hair = 0;

cout << s.major << endl;
cout << t.title << endl;
cout << p.hair << endl;
}

权限控制的三个关键字 ⭐⭐⭐

private::私有属性,其他对象无法访问

protected::受保护属性,其他对象无法访问,但是子类可以访问

public::公共属性,其他对象都可以访问

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>
#include <string>
using namespace std;

class Father {
private:
int password;
protected:
float age;
public:
string name;

void fatherShow() { // 自己可以访问所有权限的属性
cout << password << endl;
cout << age << endl;
cout << name << endl;
}
};

class Son : public Father {
void sonShow() {
cout << password << endl; //不能访问父类的private属性
cout << age << endl; //可以访问protected属性
cout << name << endl; //可以访问public属性
}
};

void main() {
Father f = Father();
cout << f.password << endl; //在类之外,不能访问
cout << f.age << endl; ///在类之外,不能访问
cout << f.name << 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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <string>
using namespace std;

class Father {
public:
int a;
protected:
int b;
private:
int c;
};

class Son1 : public Father { // 共有继承:所有的属性和方法都从父类继承下来,但是子类仍然无法访问 private 属性
public:
void show() {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
};

class Son2 : protected Father { //受保护继承:所有的属性和方法都从父类继承下来,但是 private 属性变成自己的 protected 属性
public:
void show() {
cout << a << endl;
cout << b << endl;
cout << c << endl; // 不能访问
}
};

class Son3 : private Father { // 私有继承:所有的属性和方法都从父类继承下来,public 和 protected 属性都变成自己的 private 属性
public:
void show() {
cout << a << endl;
cout << b << endl;
cout << c << endl; // 不能访问
}
};

class GrandSon : public Son3 { //
public:
void show() {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
};

void main() {
Father f;
cout << f.a << endl; //可以访问public
cout << f.b << endl; //不能访问
cout << f.c << endl; //不能访问

Son1 s1;
cout << s1.a << endl; //可以访问public
cout << s1.b << endl; //不能访问
cout << s1.c << endl; //不能访问

Son2 s2;
cout << s2.a << endl; //不行
cout << s2.b << endl; //不能访问
cout << s2.c << endl; //不能访问

Son3 s3;
cout << s3.a << endl; // 不行
cout << s3.b << endl; // 不行
cout << s3.c << 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
#include <iostream>
using namespace std;

class Father {
public:
Father() {
cout << "Father()" << endl;
}

~Father() {
cout << "~Father()" << endl;
}
};

class Son : public Father { // 继承父类的所有的属性和方法(包括构造函数和析构函数)
public:
Son() {
cout << "Son()" << endl;
}

~Son() {
cout << "~Son()" << endl;
}
};

void test() {
//Father f;
Son s;
}

void main() {
test();
}

继承的思想:is a

1
class son : public Father{}

或者另一种方式:包含的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Son{
public:
Father s;

Son() {
cout << "Son()" << endl;
}

~Son() {
cout << "~Son()" << endl;
}
};

Son s1;
s1.s.a = 10; //访问父类的属性,比较繁琐

override ⭐⭐⭐⭐⭐

子类继承父类后,可以重写该方法

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
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
char dream;
};

class Father {
public:
char dream;

Father() : dream('b') {}

void study() {
cout << "Father::study() " << "biology" << endl;
}

void study(string a) {
cout << "Father::study(string a) " << a << endl;
}
};

class Son : public Father {
public:
char dream;

Son() : dream('c') {} // override 父类的属性

void study() { // override 父类的study()函数,不带参的那个函数
cout << "Son::study() " << "computer science" << endl;
}
};

//cl /d1 reportSingleClassLayout[class] file_name //一个查看继承结构的工具
void main() {
Son s;

cout << s.dream << endl;
cout << s.Father::dream << endl; // 通过这种方式可以访问利用子类来访问父类的属性
cout << s.Son::dream << endl; // 输出效果同第一种,是一种显示的调用子类的属性

s.study();
s.Father::study();
s.Son::study();

s.study("music"); //子类带参的函数study没有复写,所以不能这样使用
s.Father::study("music"); //父类的带参函数可以使用
}

子类 Son 重写了父类 Father 的同名函数 study(),导致父类的所有重载函数被隐藏。这是 C++ 的一个特性,称为 名称隐藏(Name Hiding)

多重继承 ⭐⭐⭐

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 Father {
public:
int height;
int age;
};

class Mother {
public:
int beauty;
int age;
};

class Son : public Father, public Mother { // 同时继承父亲和母亲的类
};

//cl /d1 reportSingleClassLayout[class] file_name
void main() {
Son s;

s.Father::age = 45;
s.Mother::age = 40;

cout << s.age << endl; // 报错!!! age属性是重名的,需要单独访问,如下:
cout << s.Father::age << endl; // 45
cout << s.Mother::age << endl; // 40
}

第十一讲

菱形继承 ⭐⭐⭐⭐⭐

尽量不要用,关系比较复杂

虚继承

虚继承指的的并不是真的继承父类的属性,而是子类一个指针指向父类的对应属性,因此可以实现多个指针指向一个属性(地址),实现了多重继承,子类和父类在内存上是共用的

虚继承是为了解决菱形继承问题而提出的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

class Animal {
public:
int age;
};

class Yang: public Animal {};
class Tuo: public Animal {};
class YangTuo : public Yang, public Tuo {};

void main() {
YangTuo s;

s.age = 18; // 这行会直接报错,因为不知道子类的age指代哪个父类
// 所以只能分开访问:
s.Yang::age = 18;
s.Tuo::age = 28;
}

下面使用虚继承的方式来解决上述问题

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 Animal {
public:
int age;
};

// class Yang: public Animal {};
// class Tuo: public Animal {};

class Yang : virtual public Animal {}; // 并不是真的继承父类的属性,子类并没有age属性,而是创建一个指针,指向父类的属性
class Tuo : virtual public Animal {}; // 这两个指针都指向父类的同一个属性,因此不会产生指向冲突

class YangTuo : public Yang, public Tuo {}; // Yang和Tuo都继承了Animal,但是Animal是虚类,因此YangTuo也继承了Animal

//cl /d1 reportSingleClassLayout[class] file_name
void main() {
YangTuo s;

s.age = 18; // 三个类的age都是18
s.Yang::age = 18; // 还是18
s.Tuo::age = 28; // 三个类的age都成了28,因为都是同一个age
}

Yang的一个指针指向父类的属性age,Tuo的一个指针也指向父类的属性age,这样 YangTuo 无论从哪边访问属性age都不会产生歧义

加法运算操作符重载 ⭐⭐⭐⭐⭐

操作符本质上都是函数,像 + - * / 数组([])等都是操作符

操作符重载就是改变操作符的功能(行为)

操作符重载可以有两种方法,第一种是在类里面定义,第二种是在类外面定义,同时存在时,第一种方法优先级更高

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 Person {
public:
int age;

Person() : age(18) {}

Person add(const Person& p) { //可以使用函数来实现两个对象之间的属性相加,但是相比于操作符重载更繁琐
Person temp;
temp.age = this->age + p.age;
return temp; // 返回一个临时对象,是没有问题的!!!
}

// 第一种重载方法:重载加法运算符 可以理解为一个函数就好
Person operator+(const Person& p) {
Person temp;
temp.age = age + p.age;
return temp;
}
};

// 第二种重载方法:在外面定义操作符重载
Person operator+(const Person& p1, const Person& p2) { // 这里const的作用是表明:只读取对象的属性,但是不修改其值
Person temp;
temp.age = p1.age + p2.age;
return temp;
}

void main() {
int a = 1, b = 1;
int c = a + b;

Person p1, p2;
Person p3 = p1.add(p2); // p3.age = 18 + 18 = 36
Person p4 = p1.operator+(p2); // 调用了第一种重载方法,把操作符直接当函数调用即可
Person p4 = operator+(p1, p2); // 调用了第二种重载方法
Person p4 = p1 + p2; // 当方法1和2同时存在,会优先调用第一种

cout << p3.age << endl;
cout << p4.age << endl;
}

如果重载操作符返回的类型为引用,会产生内存错误,地址不存在,如下:

原因是,当返回的是对象时,会在外面进行一次赋值操作,接返回的内容,但是如果返回的是引用,该返回对象会随着栈空间的消失而不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  // 在类中的函数这样定义
Person& operator+(const Person& p) {
Person temp;
temp.age = age + p.age;
return temp;
}

// 在main中需要用& 来接返回值,否则会发生强制转换,并不会引发内存错误
void main() {
Person p1, p2;
Person& p4 = p1 + p2;
cout << p4.age << endl; // 输出36
// 再次打印输出将会发生内存错误
cout << p4.age << endl; // 输出不知道
cout << p4.age << endl; // 输出不知道
cout << p4.age << endl; // 输出不知道
}

但是上述代码在我自己的电脑上运行都是输出36,并没有和老师上课讲的内容一致,我也不知道为什么,但是上述分析的原理应该是没有问题的,可能电脑问题hhhhhh

关系运算符重载

这个比较简单

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
#include <iostream>
using namespace std;

class Person {
public:
int id;
int age;

Person(int _id, int _age) : id(_id), age(_age) {}

bool operator==(const Person& p) {
return (id==p.id && age==p.age);
}

bool operator!=(const Person& p) {
return (id != p.id || age != p.age);
}
};

void main() {
Person p1(0, 18);
Person p2(1, 28);

cout << (p1 == p2) << endl;
cout << (p1 != p2) << 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
#include <iostream>
using namespace std;

class Person {
public:
int* age;

Person(int _age) : age(new int(_age)) {}

~Person() {
delete age;
}

Person(const Person& p) { // 拷贝构造函数---深拷贝
age = new int(*p.age);
return *this;
}
// 使用初始化列表的方式重写拷贝构造函数---深拷贝
Person(const Person& p) : age(new int(*p.age)) {}

Person& operator=(const Person& p) {
if (age != NULL) {
delete age;
}

age = new int(*p.age);
return *this;
}
};

void test() {
Person p1(18);
Person p2(28);

p2 = p1;
//Person p2 = p1;

//Person p3(38);
//p3 = p2 = p1; // 赋值运算符的连锁调用
//cout << *p3.age << *p2.age << *p1.age << endl;
}

void main() {
while (1) {
test();
}
}
1
2
3
4
void test() {
Person p1(18);
Person p2 = p1; // 如果这样写,不是调用赋值运算符,而是调用默认的拷贝构造函数,是浅拷贝!!!!
}

浅拷贝会造成重复删除的问题

第十二讲

左移运算符重载

重载输出的符号 “<<”

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 Person {
public:
int id;
int age;

Person() : id(0), age(18) {}

void operator<<(ostream& cout) { // 左移运算符重载
cout << id << "," << age;
}
};

void operator<<(ostream& cout, const Person& p) {
cout << p.id << "," << p.age;
}

ostream& operator<<(ostream& cout, const Person& p) {
cout << p.id << "," << p.age;
return cout;
}

void main() {
Person p;
cout << p.id << "," << p.age << endl;
cout << p;

//p.operator<<(cout);
//p << cout;

//operator<<(cout, p);
//cout << p;

//Person p1;
//operator<<(operator<<(cout, p), p1);
//cout << p << p1;
////cout << p << p1 << 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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
using namespace std;

class Person {
public:
int age;

Person() : age(18) {}

Person& operator++() { // 这里针对返回的为 引用 还是 对象 ,下面打印的结果是不一样的,需要结合程序来理解
++age;
return *this;
}

Person operator++() {
++age;
return *this;
}

Person& operator++(int) { // 这里多添加了一个int,来表示 后加
Person temp = *this;
++age;
return temp;
}

Person operator++(int) { // 后加需要返回对象,中间有一个浅拷贝
Person temp = *this;
++age;
return temp;
}

const Person operator++(int) { // 返回的对象不能修改,该操作会将后加的连锁调用视为报错,起到了提醒的作用
Person temp = *this;
++age;
return temp;
}
};

ostream& operator<<(ostream& cout, const Person& p) { // 重载左移运算符
cout << p.age;
return cout;
}

void main() {
int a = 18;
Person p;

cout << ++a << endl; // 19
cout << a << endl; // 19
cout << ++(++a) << endl; //21
cout << a << endl; //21

cout << ++p << endl;
cout << p << endl;
cout << ++(++p) << endl; // 针对重载函数返回的为引用还是指针,分开讨论:
// 若返回类型为指针,可以实现连加,若返回引用,不能实现连加!!!
cout << p << endl;

cout << a++ << endl;
cout << a << endl;
cout << (a++)++ << endl; // 后加 不支持连锁调用!!!
cout << a << endl;

cout << p++ << endl;
cout << p << endl;
cout << (p++)++ << endl;
cout << p << endl;
}

仿函数 functor

functor 函数是对象

能够实现的功能有:可以保存函数执行过程中的中间变量

就是对 括号 的重载

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 adder(int a, int b) {
return a + b;
}

class Adder {
public:
int operator()(int a, int b) { // 创建一个类来重载一个括号运算符,叫 functor
return a + b;
}
};

void main() {
Adder adder;

cout << adder(2, 3) << endl;
}

第十三讲

面向对象编程的第三大特性---多态 polymorphism ⭐⭐⭐⭐⭐

多态:能否将父类的对象转为子类的对象?

结论:

  • 父类对象直接等于子类对象是可以的,但是没有变形成功
  • 子类对象直接等于父类对象是不行的,会报错

多态分为动态多态和静态多态

函数重载就是静态多态

实现多态使用到的原理:

  • 虚函数来实现晚绑定(虚函数与虚继承没有任何关系!)
  • 对于父类的函数使用虚函数来实现就可以实现多态

实现多态的三大要点:

  • 父类对象直接等于子类对象是不行的,要父类的 指针或者引用 等于子类对象才行
  • 使用虚函数实现晚绑定
  • 子类需要重写相关函数

指针和引用是实现多态的关键!!!

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 Animal {
public:
int age;

Animal() : age(18) {}

void shout() {
cout << "Shout: " << age << endl;
}
};

class Cat : public Animal {
public:
char color;

Cat() : color('y') {}

void shout() { // 重写父类的 shout 函数
cout << "Meow: " << age << color << endl;
}
};

void main() {
Animal a;
Cat c;

c.shout();
c.Animal::shout();

Animal ap = c; // 试图利用子类创建一个父类对象,但是直接转换是无法成功的
Cat cp = a; // 利用父类对象创建一个子类对象会报错,因为属性不对齐

Animal* ap = &c; // 采用指针的形式来实现变形(子类->父类)
Cat* cp = &a;

Animal& ap = c; // 使用引用也能实现多态,引用本质上也是指针
Cat& cp = a;

ap.shout();
}

如何实现多态?需要使用到虚函数

虚函数可以实现函数与类之间的晚绑定

辨析:虚继承为了解决菱形继承问题提出的,虚函数则是为了实现晚绑定

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
#include <iostream>
using namespace std;

class Animal {
public:
// void shout() {
// cout << "Shout" << endl;
// }

virtual void shout() { // 父类对象需要使用虚函数实现晚绑定
cout << "Shout" << endl;
}
};

class Cat : public Animal {
public:
void shout() {
cout << "Meow" << endl;
}
};

class Dog : public Animal {
public:
void shout() {
cout << "Woof" << endl;
}
};

void test(Animal* a) { // 指针实现多态
a->shout();
}

void test(Animal& a) { // 引用也可以实现多态
a.shout();
}

void main() {
Cat c;
Dog d;

test(&c); // 调用指针的多态需要传地址
test(&d);

test(c); // 调用引用的多态时直接传对象
test(d);
}

纯虚函数和抽象类 ⭐⭐⭐⭐⭐

纯虚函数的定义:定义一个类时使用到了前面讲的虚函数,但是不指定任何类的行为,类似下面的定义形式

1
2
3
4
class Animal {
public:
virtual void shout() = 0; // 这里的shout是纯虚函数,没有实际的行为
};

抽象类的定义:含有纯虚函数的类叫抽象类,不能用来创建对象,只能被继承

继承的子类必须重写纯虚函数,才能实现创建对象

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;

class Animal {
public:
virtual void shout() = 0;
};

class Cat : public Animal {
};

class Dog : public Animal {
public:
void shout() {
cout << "Woof" << endl;
}
};

void test(Animal* a) {
a->shout();
}

void main() {
Animal a; // 抽象类不能用来创建对象
Cat c; // 不重写的子类也不能用来创建对象

Dog d;
Animal* a = &d;
a->shout();
}

虚析构函数 ⭐⭐⭐⭐⭐

在实现多态时可能会存在内存泄漏的问题,原因是因为在删除堆空间的子类时,调用的是父类的析构函数,而父类的析构函数并没有释放子类中的堆空间,所以导致内存泄漏。

因此需要在父类的析构函数中添加一个虚析构函数,这样就会调用子类的析构函数,从而释放子类中的堆空间。

同时需要注意一点就是父类的虚析构函数还不能是纯虚析构(必须定义具体的行为),因为虚构函数是有意义的,无论是子类还是父类

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
#include <iostream>
using namespace std;

class Animal {
public:
Animal() {
cout << "Animal()" << endl;
}

~Animal() { //普通析构会造成内存泄漏
cout << "~Animal()" << endl;
}

virtual ~Animal() { // 必须使用-虚析构函数
cout << "~Animal()" << endl;
}

// 下面的行为是错误的!!!
// 虚析构函数不能是纯虚析构
virtual ~Animal() = 0;

// 普通函数可以是纯虚析构
virtual void shout() = 0;
};

Animal::~Animal() {
cout << "Animal::~Animal()" << endl;
}

class Cat : public Animal {
public:
int* age;

Cat() : age(new int(18)) {
cout << "Cat()" << endl;
}

~Cat() {
delete age;
cout << "~Cat()" << endl;
}

void shout() {
cout << "Meow: " << *age << endl;
}
};

void test() {
Animal* a = new Cat();
a->shout();
delete a; // 因为父类是虚析构,所以会调用子类cat的析构函数来删除内存
// 否则调用的是父类的析构函数,导致内存泄漏
}

void main() {
test();
}

第十四讲 模板和异常

函数模板 ⭐⭐⭐

函数模板的作用就是优化函数重载需要针对特定的数据类型,不是很方便的问题

函数模板可以 Template,也可以 Template 先定义函数的具体功能,在使用时再传入数据类型,不像函数重载需要在一开始就确定数据类型,因此函数模板更加灵活。

函数模板解决的是函数中的传入参数不确定的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

// 函数模板的两种定义方式
template<typename T>
template<class T>

void my_swap(T& a, T& b) {
T t = a;
a = b;
b = t;
}

void main() {
int a = 10;
int b = 20;

//my_swap(a, b);
my_swap<int>(a, b); // 在调用函数时再传入数据类型,更加灵活方便
cout << a << endl;
cout << b << 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
#include <iostream>
using namespace std;

template<class IdType, class AgeType>
class Person {
public:
IdType id;
AgeType age;

Person(IdType _id, AgeType _age) : id(_id), age(_age) {}
};

// 类模板的第一种创建方法---在继承父类时确定父类的属性
template<class StuIdType>
class Student :public Person<int, int> {
public:
StuIdType stu_id;

Student(StuIdType _stu_id, int _id, int _age) : stu_id(_stu_id), Person<int, int>(_id, _age) {}
};

// 类模板的第二种创建方法---在继承父类时不确定父类的属性
template<class StuIdType, class IdType, class AgeType>
class Student :public Person<IdType, AgeType> {
public:
StuIdType stu_id;

Student(StuIdType _stu_id, IdType _id, AgeType _age) : stu_id(_stu_id), Person<IdType, AgeType>(_id, _age) {}
};

void main() {
Person<int, int> p(1, 18); // 调用有参构造函数
Person<int, int> p = Person<int, int>(1, 18); // 同上,形式不同
cout << p.id << ", " << p.age << endl;

Student<int> s(0, 18, 1); // 对应类模板的第一种创建方法
Student<int, int, int> s(0, 18, 1); // 对应类模板的第二种创建方法
cout << s.id << ", " << s.age << ", " << s.stu_id << endl;
}

类模板 vector ⭐

vector是一个常见的类模板,下面介绍了一下常见的用法

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
#include <iostream>
#include <vector>
using namespace std;

void print(vector<int>& v) { // 把对象的引用传入 print 函数
// vector 中有个迭代器能够实现遍历 vector
for (vector<int>::iterator i = v.begin(); i != v.end(); ++i) {
cout << *i << ' ';
}
cout << endl;
}

void construct(vector<int>& v) {

// v1 从 v 的开头到结尾一个一个复制过来
vector<int> v1(v.begin(), v.end()); // 是有参构造函数
print(v1);

// v2 会构造一个8个6的 vector
vector<int> v2(8, 6); // 也是有参构造函数,与上面构成了函数重载
print(v2);

vector<int> v3(v2); // 拷贝构造函数
print(v3);
}

// 赋值
void assign(vector<int>& v) {
vector<int> v1 = v; // 这里调用的是拷贝构造函数!!!非常容易理解错误
print(v1);

vector<int> v2; // v2先调用无参构造函数
v2.assign(v.begin(), v.end()); // 然后实现从v到v2的依次赋值
print(v2);

vector<int> v3;
v3.assign(8, 6);
print(v3);
}

void length(vector<int>& v) {
cout << v.empty() << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;

for (int i = 0; i < 32; ++i) {
v.push_back(i);
cout << v.capacity() << ' ';
}
cout << endl;

v.resize(8);
print(v);
cout << v.size() << endl;
cout << v.capacity() << endl;

v.resize(12);
print(v);

v.resize(16, 1);
print(v);
}

void insert_delete(vector<int>& v) {
v.pop_back();
print(v);
cout << v.capacity() << endl;

v.insert(v.begin(), 10);
print(v);

v.insert(v.begin(), 2, 18);
print(v);

v.erase(v.begin());
print(v);
cout << v.capacity() << endl;

v.erase(v.begin() + 1, v.end());
print(v);
cout << v.capacity() << endl;

v.clear();
print(v);
cout << v.capacity() << endl;
}

void access(vector<int>& v) {
cout << v.at(2) << endl;
cout << v[2] << endl;
cout << v.front() << endl;
cout << v.back() << endl;
}

void swap(vector<int>& v) {
vector<int> v1;
print(v1);
cout << v.capacity() << endl;
cout << v1.capacity() << endl;

cout << "After swap" << endl;
v.swap(v1);
print(v1);
cout << v.capacity() << endl;
cout << v1.capacity() << endl;
}

void shrink(vector<int>& v) {
cout << v.capacity() << endl;
vector<int>().swap(v);
cout << v.capacity() << endl;
}

void main() {
vector<int> v; // 创建一个 vector 对象,每个元素的类型为 int(是一个类模板)
for (int i = 0; i < 8; ++i) {
v.push_back(i);
}
print(v);

construct(v);
assign(v);
length(v);
insert_delete(v);
access(v);
swap(v);
shrink(v);
}

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
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
void main() {
Circle c = Circle(2); //Circle with radius 2
Rectangle r = Rectangle(2, 3); //Rectangle with length 2 and width 3
Square s = Square(2); //Square with length 2
Triangle t = Triangle(2); //Regular triangle with length 2
Sphere sp = Sphere(2); //Sphere with radius 2
Cylinder cy = Cylinder(2, 4); //Cylinder with radius 2 and height 4
Cuboid cd = Cuboid(2, 3, 4); //Cuboid with length2, width 3, and height 4
Cube cb = Cube(2); //Cube with length 2
Prism ps = Prism(2, 5); //Regular triangular prism with length 2 and height 5
Tetrahedron tt = Tetrahedron(2); //Tetrahedron with length 2
const int n = 10;
Shape* ss[n] = {&c, &r, &s, &t, &sp, &cy, &cd, &cb, &ps, &tt};
for (int i = 0; i < n; ++i) {
cout << ss[i]->area() << endl;
}
}
// Result:
12.5664
6
4
1.73205
33.5103
50.2654
24
8
8.66025
0.942809
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
#include <iostream>
#include <cmath>
using namespace std;
const double PI = 3.14159;

// Base class for all shapes
class Shape {
public:
virtual ~Shape() {};
virtual double area() = 0;
};

class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() {
return PI * radius * radius;
}
};

class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() {
return width * height;
}
};

class Square : public Shape {
private:
double side;
public:
Square(double s) : side(s) {}
double area() {
return side * side;
}
};

class RegularTriangle : public Shape {
private:
double side;
public:
RegularTriangle(double s) : side(s) {}
double area() {
return (sqrt(3) / 4) * side * side;
}
};

class Sphere : public Shape {
private:
double radius;
public:
Sphere(double r) : radius(r) {}
double area() {
return (4.0 / 3.0) * PI * radius * radius * radius;
}
};

class Cylinder : public Shape {
private:
double radius, height;
public:
Cylinder(double r, double h) : radius(r), height(h) {}
double area() {
return PI * radius * radius * height;
}
};

class Cuboid : public Shape {
private:
double width, height, depth;
public:
Cuboid(double w, double h, double d) : width(w), height(h), depth(d) {}
double area() {
return width * height * depth;
}
};

class Cube : public Shape {
private:
double side;
public:
Cube(double s) : side(s) {}
double area() {
return side * side * side;
}
};

class RegularTriangularPrism : public Shape {
private:
double baseSide, height;
public:
RegularTriangularPrism(double b, double h) : baseSide(b), height(h) {}
double area() {
return (sqrt(3) / 4) * baseSide * baseSide * height;
}
};

class Tetrahedron : public Shape {
private:
double edge;
public:
Tetrahedron(double e) : edge(e) {}
double area() {
return (edge * edge * edge) / (6 * sqrt(2));
}
};

void main() {
Circle c = Circle(2); //Circle with radius 2
Rectangle r = Rectangle(2, 3); //Rectangle with length 2 and width 3
Square s = Square(2); //Square with length 2
RegularTriangle t = RegularTriangle(2); //Regular triangle with length 2
Sphere sp = Sphere(2); //Sphere with radius 2
Cylinder cy = Cylinder(2, 4); //Cylinder with radius 2 and height 4
Cuboid cd = Cuboid(2, 3, 4); //Cuboid with length2, width 3, and height 4
Cube cb = Cube(2); //Cube with length 2
RegularTriangularPrism ps = RegularTriangularPrism(2, 5); //Regular triangular prism with length 2 and height 5
Tetrahedron tt = Tetrahedron(2); //Tetrahedron with length 2
const int n = 10;
Shape* ss[n] = { &c, &r, &s, &t, &sp, &cy, &cd, &cb, &ps, &tt };
for (int i = 0; i < n; ++i) {
cout << ss[i]->area() << endl;
}
}

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
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
#include<iostream>
#include<cstring>
using namespace std;

class my_str
{
char* str;
int len;

my_str(const char* s = nullptr)
{
if (s == nullptr)
{
len = 0;
str = new char[1];
str[0] = '\0';
}
else
{
len = strlen(s);
str = new char [len + 1];
strcpy(str, s);
}
}
~my_str()
{
delete[] str;
}

my_str(const my_str& other)
{
len = other.len;
str = new char[len + 1];
strcpy(str, other.str);
}

my_str& operator=(const my_str& other)
{
if (this != &other)
{
delete[] str;
len = other.len;
str = new char[len + 1];
strcpy(str, other.str);
}
return *this;
}

bool operator==(const my_str& other)
{
return strcmp(str, other.str) == 0;
}

bool operator!=(const my_str& other)
{
return strcmp(str, other.str) != 0;
}

bool operator<(const my_str& other)
{
return strcmp(str, other.str) < 0;
}

bool operator>(const my_str& other)
{
return strcmp(str, other.str) > 0;
}

friend ostream& operator<<(ostream& os, const my_str& s)
{
os << s.str;
return os;
}

my_str operator+(const my_str& s)
{
my_str result;
result.len = s.len + len;
result.str = new char[len + 1];
strcpy(result.str, str);
strcat(result.str, s.str);
return result;
}
};

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
2
3
4
5
6
7
8
#include<iostream>
using namespace std;
int main()
{
cout<<"hello !"<<endl;
cout<<"I am a student."<<endl;
return 0;
}

使用iostream的时候,必须使用namespace std,这样才能正确的使用命名空间std封装的标准程序库中的标识符cin、cout等

如何将一段代码变为可执行的程序,即如何编译编写好的程序:

1
g++ name.cpp -o name.exe

常量

常量指的是在程序执行过程中不会改变的量

一、字面常量
- 把常量书写到代码内部,叫字面常量 - 可以有整数(整型)、小数(实型),二者统称为数值型常量 - 字符型字面常量:由单引号包围的单个字符 - 字符串字面常量:以双引号包围的,任意个数个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "iostream"
using namespace std;

int main()
{
21;//整型
180.5;//实型
'a';//字符型字面常量
"abc";//字符串字面常量

//打印
cout<<21<<endl;
cout<<180.5<<endl;
cout<<'a'<<endl;
cout<<"abc"<<endl;

return 0;
}

二、符号常量
用标识符去定义的常量,给常量一个名字,就是符号常量 - 在c语言中使用编译预处理指令#define定义符号常量:#define PI 3.14 - C++中在类型前添加关键词const来定义:const double PI=3.14 const int N = 6 - 符号常量的值必须在定义时指定,且不能赋新值

输入输出

cin数据输入

语法:

1
2
数据类型 变量;  //声明变量
cin >> 变量 //对变量进行赋值

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main()
{
int num;
cout << "请输入一个整数:" << endl;
cin >> num;
cout << num;
return 0;
}

cout打印输出

输出单份内容

1
2
cout <<"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;
- 2、变量的赋值 - 语法:
1
num=10;
二、变量的快捷定义: - 1、声明和赋值同步进行
1
int age = 10;

  • 2、一次性声明多个变量
    1
    2
    3
    4
    int a,b,c;
    a=10;
    b=20;
    c=30;
  • 3、结合1、2
    1
    int a=10,b=20,c=30;

三、常见的变量类型:

1
2
3
4
int  //整型
float //实型
char //字符型
string //字符串型

变量的基本特征

变量存储的数据是可以发生改变的,支持: - 多次赋值语句 - 各种数学运算

示例:

1
2
3
4
5
6
7
8
9
10
int 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
2
3
4
5
// 有符号数中的signed可以省略
signed int = int

// 无符号数中的unsigned不可省略
unsigned int

数据类型-实型

实型数据默认有符号数

1
2
3
float    单精度浮点型  4字节
double 双精度浮点型 8字节
long double 多精度浮点型 16字节

数据类型-字符串

C语言风格的字符串:

1
2
char a[] = "Hello World"; //字符数组形式
char *b = "Hello World"; //指针形式
C++风格字符串:
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
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;

int main()
{
string a = "疯狂星期四";
string b = "V me ";
int d = 50;
string c = a + "," + b + to_string(d); //将50转为字符串类型
cout << c << endl;
return 0;
}

在 C 语言中,字符串实际上是以空字符 '\0' 结尾的字符数组。以下是常见的字符串拼接方法:

  1. 使用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 所指向的字符串内容。
  2. 手动拼接
    • 思路:通过遍历两个字符串,将第二个字符串的字符逐个复制到第一个字符串的末尾空字符之后。
    • 示例:
      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++字符串拼接的区别

  1. 数据类型和安全性
    • C 语言使用字符数组来表示字符串,容易出现缓冲区溢出等安全问题,因为它不进行边界检查。
    • C++的std::string类会自动管理内存,进行边界检查,并且在拼接时会自动处理内存分配,相对更安全。
  2. 操作便捷性
    • C 语言需要借助strcat函数或手动复制字符来实现拼接,代码相对繁琐。
    • C++使用+运算符和append方法,操作更加直观和便捷,代码更简洁易读。
  3. 头文件
    • C 语言的字符串操作函数通常包含在<string.h>头文件中。
    • C++的std::string相关操作包含在<string>头文件中。

数据类型-布尔型

在程序中表达互斥的概念

1
2
true  1
false 0

用数据类型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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<cctype>
using namespace std;

void main()
{
char a = 'H';
char b = '3';
char c = ' ';
char d = ',';
if ((a >= 'a' and a <= 'z') or (a >= 'A' and a <= 'Z'))
cout << "是一个字符" << endl;
if (isalpha(a))
cout << "是一个字符too" << endl;
//是一个字符
//是一个字符too
if (isdigit(b))
cout << "测试字符是一个数字" << endl;
if (isspace(c))
cout<<"测试字符是一个空白" << endl;
if (ispunct(d))
cout<<"测试字符是一个标点" << endl;
}

运算符

C++内置的运算符有: - 算数运算符 - 赋值运算符 - 比较运算符 - 逻辑运算符 - 位运算符 - 三目运算符

一、算术运算符 - 加法运算符(+)

1
2
3
int a = 5;
int b = 3;
int c = a + b; // c 的值为 8
- 减法运算符(-)
1
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
6
int 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
3
bool p = true;
bool q = false;
bool r = p && q; // r 为 false,因为只有当两个操作数都为 true 时,结果才为 true
- 逻辑或运算符(||)
1
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
3
int m = 5; // 二进制为 0101
int n = 3; // 二进制为 0011
int o = m & n; // 二进制 0101 & 0011 = 0001,即 o 的值为 1
- 按位或运算符(|)
1
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
2
3
if (condition) {
// 如果条件为真(true)执行的代码块
}

这里的condition是一个布尔表达式(结果为truefalse)。如果conditiontrue,则执行花括号{}内的代码块;如果为false,则跳过该代码块。

例如:

1
2
3
4
int x = 5;
if (x > 3) {
std::cout << "x 大于 3" << std::endl;
}

在这个例子中,因为5大于3,所以条件x > 3true,会执行输出语句

2. if-else结构

1
2
3
4
5
if (condition) {
// 如果条件为真执行的代码块
} else {
// 如果条件为假执行的代码块
}

当condition为true时执行if块中的代码,否则执行else块中的代码

例如:

1
2
3
4
5
6
int y = 2;
if (y > 3) {
std::cout << "y 大于 3" << std::endl;
} else {
std::cout << "y 不大于 3" << std::endl;
}

这里因为2不大于3,所以会执行else块中的代码。

3. if-else if-else结构

1
2
3
4
5
6
7
8
9
if (condition1) {
// 如果条件 1 为真执行的代码块
} else if (condition2) {
// 如果条件 2 为真执行的代码块
} else if (...) {
// 更多的 else if 条件和代码块
} else {
// 如果所有条件都为假执行的代码块
}

可以根据多个条件进行判断,依次检查每个条件,直到找到一个为真的条件并执行相应的代码块,或者如果所有条件都为假,则执行最后的else块中的代码。

例如:

1
2
3
4
5
6
7
8
int z = 7;
if (z < 5) {
std::cout << "z 小于 5" << std::endl;
} else if (z < 10) {
std::cout << "z 大于等于 5 且小于 10" << std::endl;
} else {
std::cout << "z 大于等于 10" << std::endl;
}

在这个例子中,因为7大于等于5且小于10,所以会执行第二个else if块中的代码。

4. 嵌套的 if 语句

可以在ifelseelse if代码块中嵌套另一个if语句。

例如:

1
2
3
4
5
6
7
int a = 3;
int b = 4;
if (a > 2) {
if (b > 3) {
std::cout << "a 大于 2 且 b 大于 3" << std::endl;
}
}

这里首先判断a > 2,如果为真,再判断b > 3,如果也为真,则执行输出语句。

switch-case语句

在 C++ 中,switch 控制语句是一种多路分支结构,它允许根据一个表达式的值来选择一组语句执行。

基本语法介绍:

1
2
3
4
5
6
7
8
9
10
11
switch (expression) {
case constant-expression1:
// 语句序列
break;
case constant-expression2:
// 语句序列
break;
// ...
default:
// 默认情况下的语句序列
}
规则: - expression 必须能够产生一个整数或枚举类型的值。 - case 后面的 constant-expression 必须是编译时常量,并且每个 case 标签必须唯一。 - break 语句用来终止当前 case 并跳出整个 switch 语句。 - 如果没有 break 语句,程序会继续执行下一个 case 直到遇到 breakswitch 结束。 - 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
5
enum enumeration-name {
enumerator1,
enumerator2,
// 更多枚举成员...
};
每个 enumerator 都是一个整数常量,默认情况下,第一个枚举成员的值为 0,后续的枚举成员的值依次递增 1。也可以显式地为枚举成员指定值:
1
2
3
4
5
6
enum enumeration-name {
enumerator1 = 10,
enumerator2,
enumerator3 = 20,
enumerator4
};

二、声明变量并赋值

1
2
3
4
enumeration-name variable_name;
variable_name = enumerator1; // 赋值
//或者
enumeration-name variable_name = enumerator1;

三、枚举类型的范围

默认情况下,枚举类型的底层类型取决于枚举成员的最大值。从 C++11 开始,可以指定枚举的底层类型:

1
2
3
4
5
6
7
8
9
10
//这里使用 enum class 形式定义枚举,并指定了底层类型为 unsigned char
enum class Weekday : unsigned char {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};

四、枚举成员的访问

从 C++11 开始,我们可以使用作用域解析运算符 :: 来访问枚举成员:

1
Weekday today = Weekday::Friday;

如果使用的是 enum 而不是 enum class,那么枚举成员会被提升到作用域内,可以直接使用它们的名字:

1
Weekday today = Friday;
### while循环语句 在 C++ 中,while 循环语句是一种控制结构,它允许我们重复执行一段代码,只要给定的条件为真(非零), while 循环的基本语法如下:
1
2
3
4
while (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: 5050
### do-while循环语句 do-while 循环是 C/C++ 语言中的一种循环控制结构,它的特点是至少会执行一次循环体内的代码,然后才检查循环条件。这与 while 循环不同,在 while 循环中,如果初始条件就不满足,则循环体内的代码不会被执行。

1
2
3
4
do {
// 循环体
// 这里的代码会至少执行一次
} while (condition); //注意这里有分号收尾

do-while 循环的工作流程:
1、执行循环体:首先执行循环体内的代码。
2、条件检查:执行完循环体后,检查 condition 是否为真。
3、重复步骤 1 和 2:如果 condition 为真,则再次执行循环体。这个过程会一直重复,直到 condition 变为假为止。

for循环语句

for 循环是 C/C++ 语言中的一种循环控制结构,它允许你重复执行一段代码,通常用于已知循环次数的情况。for 循环提供了初始化条件检查迭代三个部分在一个简洁的语法中。

基本语法:

1
2
3
4
for (initialization; condition; iteration) {
// 循环体
// 这里的代码会在条件为真时重复执行
}
- Initialization:初始化部分只执行一次,通常用于设置循环变量的初始值。 - Condition:条件部分在每次循环开始之前被检查。如果条件为真(非零),则执行循环体。如果条件为假,则退出循环。 - Iteration:迭代部分在每次循环体执行完毕后执行,通常用于更新循环变量。

工作流程:
- 1、初始化:首先执行初始化部分。 - 2、条件检查:然后检查条件部分。如果条件为真,则执行循环体内的代码。 - 3、执行循环体:执行完循环体后,执行迭代部分。 - 4、重复步骤 2 和 3:再次检查条件部分,如果条件仍然为真,则再次执行循环体。这个过程会一直重复,直到条件变为假为止。

特殊用法:
- 1、空循环体:可以在 for 循环中使用空循环体,通常用于延时或等待操作。 - 2、省略部分:for 循环的初始化、条件和迭代部分都可以被省略,但分号 ; 必须保留。 - 3、多个变量:可以在初始化和迭代部分声明和更新多个变量。

循环中断语句

在 C++ 中,我们可以使用 breakcontinue 语句来中断循环。 - break 语句用于中断循环,即跳出循环。 - continue 语句用于跳过当前循环的剩余部分,直接进入下一次循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int main() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当 i 等于 5 时,退出循环
}
cout << i << " ";
}
cout << "Loop ended." << endl;
return 0;
}
// 1 2 3 4 Loop ended.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int main() {
int sum = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 当 i 是偶数时,跳过本次循环的剩余部分
}
sum += i; // 只对奇数求和
}
cout << "Sum of odd numbers from 1 to 10 is: " << sum << endl;
return 0;
}
// Sum of odd numbers from 1 to 10 is: 25

goto语句

goto 语句是一种无条件跳转语句,它允许程序直接跳转到程序中的另一个指定位置。goto 语句在早期的编程实践中非常常见,但在现代编程风格中,它通常被认为是不良实践,因为它可能导致程序难以理解和维护。

1
2
3
goto 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.
在大多数情况下,可以使用 if、while、for 和 do-while 等结构化的控制语句来替代 goto

输入信息控制循环

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() {
char ch;
int count = 0;
cout << "输入字符串,以#结束:";
while (true) {
cin >> ch;
if (ch == '#') {
break;
}
count++;
}
cout << "共输入" << count << "个字符" << endl;
return 0;
}
// 输入字符串,以#结束:abcdefg#
// 共输入7个字符

输出杨辉三角

数组

数组的特点: - 1、有序性:数组中的元素是有顺序的,可以通过索引来访问特定位置的元素 - 2、同类型:数组中的所有元素必须是相同的数据类型 - 3、连续存储:数组的元素在内存中是连续存储的,这使得随机访问非常快

1
2
3
4
5
6
7
8
9
int 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
15
int 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
14
int 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
    3
    int 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
    4
    int 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
    6
    int 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
    3
    int *data = new int; // 分配一个int大小的空间
    *data = 42; // 设置值
    delete data; // 释放内存
  • 数组的动态分配:
    使用new[]分配数组空间,使用delete[]释放
    1
    2
    3
    int *array = new int[10];
    array[0] = 1;
    delete[] array;
    ### 函数中的指针
  • 传入指针: 可以将指针作为参数传递给函数,从而改变原函数外部的数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void setZero(int *p) {
    *p = 0;
    }

    int main() {
    int x = 5;
    setZero(&x);
    std::cout << x << std::endl; // 输出0
    }
  • 返回指针: 函数可以返回一个指针
    1
    2
    3
    4
    int *getPointer() {
    static int val = 10;
    return &val;
    }
    ### 指针与数组
  • 数组名作为指针: 数组名实际上是一个指向数组第一个元素的常量指针
    1
    2
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr; // ptr指向arr[0]
  • 指针与数组的区别: 数组名不能被重新赋值,而指针可以
    1
    2
    arr = nullptr; // 错误
    ptr = nullptr; // 正确
    ### 指针与引用
  • 引用: 引用不是指针,但它提供了一种别名的方式来访问同一变量
    1
    2
    3
    int x = 10;
    int &ref = x; // ref是x的引用
    ref = 20; // x现在等于20
  • 区别: 引用必须在声明时初始化,并且不能被改变;指针可以在任何时候被重新赋值
    1
    2
    int *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 的结果如何,整个表达式的值都将为 true
2、
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main() {
cout << "c:\\tools\book.txt";
return 0;
}
// c:\toolook.txt


cpp基础
http://jrhu0048.github.io/2024/07/06/cpp/cpp-ji-chu/
作者
JR.HU
发布于
2024年7月6日
更新于
2025年1月7日
许可协议