前言

b站C++课程学习笔记整理。

b站视频: 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难

2. Hello World

  • Visual Studio 2022 新建一个项目
  • 选择空项目,命名
  • 选择源文件,右键-添加-新建项-命名
  • 编写代码
1
2
3
4
5
6
7
8
#include <iostream>
// y
using namespace std;
int main(){
cout << "Hello world" << endl;
system("pause");
return 0;
}
  • 运行

C++ Hello Wolrd

注意:可以用\n来代替endl

3. 注释

1
2
3
4
5
// 单行注释

/*
多行注释
*/

4. 变量

和Java类似。int a = 10;

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main(){
int a = 10;
cout << "a="<< a << endl;
system("pause");
return 0;
}

5. 常量

类似于Java中final修饰的变量。

两种定义方式:

  1. # define 常量名 常量值,定义一个宏常量,写在文件上方。(注意没有分号;
  2. const 数据类型 常量名 = 常量值(类似final

示例代码:

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#define week 7
using namespace std;
int main(){
const int year = 12;
cout << "一周一共有"<< week << "天" << endl;
cout << "一年一共有" << year << "个月" << endl;
system("pause");
return 0;
}

6. 关键字

变量名不要用关键字修饰。比如变量名不可以为int

7. 标识符命名规则

给变量起名,不能是关键字,不能有空格,必须是字母、数字、下划线组成。第一个字符必须为字母或者下划线。字母区分大小写。

8. 数据类型 整型

short int long longlong。和Java一样。

9. sizeof 关键字

可以统计数据类型所占内存大小。

1
2
3
4
5
6
7
8
9
#include<iostream>
using namespace std;
int main() {
short num1 = 10;
cout << "short占用的内存空间为:" << sizeof(short) << endl;
cout << "short占用的内存空间为:" << sizeof(num1) << endl;
system("pause");
return 0;
}

sizeof查就好了

10. 实型(浮点型)

floatdouble

科学计数法:

1
2
3
4
5
6
7
8
#include<iostream>
using namespace std;
int main() {
float f = 3e2;
cout << "f等于" << f << endl;
system("pause");
return 0;
}

11. 字符型

语法:char ch = 'a';和java一样。底层是ASCII码存储。

12. 转义字符

表示一些不能显示出来的ASCII字符。都是用’\'开头的。

常用:

字符 含义
\n 换行
\t 相当于按了一下TAB
\\ 代表一个\

示例代码:

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

int main(){
cout << "hellow world\n";
cout << "\tnihao\n";
cout << "\\" << endl;

system("pause");
return 0;
}

结果:

转义符测试

13. 字符串名

两种定义方式:

  1. char 变量名[] = "字符串值"。其实可以理解成字符串数组。
  2. String 变量名 = "字符串值"

#include<string>在新版的VScode时不用加上的了。

14. 布尔数据类型

truefalse。定义方式是bool flag = false;和java的关键字不同。

15. 数据的输入

语法:

cin >> 变量

示例代码:

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

int main(){
int a = 0;
cout << "请赋值" << endl;
cin >> a;
cout << "输入的变量是" << a << endl;

system("pause");
return 0;
}

16. 加减乘除

+-*/

17. 取模

%

18. 递增递减

i++或者++i

i--或者--i

19. 赋值运算

=+=-=,*=,/=,%=

20. 比较运算

== > < >= <= !=

21-23. 与或非

&& || !

24-27. if 语句

和Java一样。

28-29. 三目运算符

c = a > b? a : b 和Java一样。

30. switch

和Java一样。

1
2
3
4
5
6
switch (a)
{
case 1:
执行;
break;
}

31-32. while

while(){}。 和Java一样。

33. do…while

do{}while()

34- 40 for循环,break,continue

和Java一样。

41. goto

无条件的跳转到想要的代码。

示例代码:

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

int main(){
cout << "1" << endl;
cout << "2" << endl;
goto FLAG;
cout << "3" << endl;
cout << "4" << endl;
FLAG:
cout << "5" << endl;

system("pause");
return 0;
}

输出:

goto的用法

但是goto最好别用

42. 数组-一维数组定义方式

三种定义方式:

  1. 数据类型 数组名[数组长度]
  2. 数据类型 数组名[数组长度] = {值1, 值2, ....}
  3. 数据类型 数组名[] = {值1, 值2, ....}

和Java那个不推荐的定义方式类似。

43. 一维数组的名称的用途

  1. 统计整个数组在内存中所占空间的长度 sizeof(arr)
  2. 获取数组在内存中的首地址。

数组中元素的个数:sizeof(arr)/sizeof(arr[0]);

取址符&

44-45. 数组元素逆置

老套路

46. 冒泡排序

老套路

47. 二维数组

四种定义方式:

  1. 数据类型 数组名[行数][列数]
  2. 数据类型 数组名[行数][列数] = {{数据1,数据2}, {数据3,数据4}}
  3. 数据类型 数组名[行数][列数] = {值1, 值2, ....}(和第二种一样,没第二种直观。)
  4. 数据类型 数组名[][列数] = {值1, 值2, ....}它自己能根据后面数据算出来是几行。

48. 二维数组的数组名

  • 查看占用的内存空间

行数:sizeof(arr)/sizeof(arr[0])

列数:sizeof(arr[0])/sizeof(arr[0][0])

元素个数: sizeof(arr)/sizeof(arr[0][0])

  • 查看首地址

二维数组的首地址、二维数组的第一行的首地址、二维数组的第一个数据的首地址是一致的。

49. 考试成绩统计

使用String要在头文件声明

1
#include <string>

50. 函数

函数的定义:

  1. 返回值类型
  2. 函数名
  3. 参数列表
  4. 函数体语句
  5. return 表达式

语法:

1
2
3
4
5
6
返回值类型 函数名 (参数列表)
{
函数体;
return xx;

}

和Java一样(没有Java那些花里胡哨的public

51. 函数的调用

和Java一样

52. 值传递

函数调用时实参将数值传入形参。形参改变不会影响实参。

53. 常见样式

  1. 无参无返(用void)
  2. 有参无返
  3. 无参有返
  4. 有参有返

54. 函数的声明

代码是一行一行执行的。如果函数在main后面,就找不到了,所以需要提前对函数声明。

int max(int a, int b)。没有花括号。

(新版的VS已经不需要声明了。例如执行如下代码,没有声明,但是是完全OK的。)

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

int main(){
int a = 1;
int b = 2;
cout << max(a, b) << endl;
}
int max(int a, int b) {
return a > b ? a : b;
}

55. 函数的分文件编写

  1. 创建后缀名为.h的头文件
  2. 创建后缀名为.cpp的源文件
  3. 在头文件写函数的声明
  4. 在源文件中写函数的定义

这个头文件,有点像Java里面的import,又有点像继承。

示例代码:

  • 头文件function.h声明了两个函数’swap’和print2nums
1
2
3
4
5
6
7
#pragma once
#include<iostream>
using namespace std;

void swap(int a, int b);
void print2nums(int a, int b);

  • 两个函数的cpp文件
1
2
3
4
5
6
7
8
9
#include "function.h";

void swap(int a, int b) {
cout << "交换前的数字是" << a << "和" << b << "\n";
int temp = a;
b = temp;
a = b;
cout << "交换前的数字是" << a << "和" << b << endl;
}

1
2
3
4
5
#include "function.h";

void print2nums(int a, int b) {
cout << "两个数字是" << a <<"+" << b << endl;
}
  • 包含main函数的cpp文件
1
2
3
4
5
6
7
8
9
10
11
#include<iostream>;
#include "function.h";
using namespace std;

void main() {
int a = 10;
int b = 20;
swap(a, b);
print2nums(a, b);
system("pause");
}

56. 指针

可以通过指针来保存一个地址。(指针就是个地址

假设现在有一个变量int a = 10;

  1. 定义指针:int * p;
  2. 使用取址符记录地址p = &a;
  3. 使用指针:可以通过解引用的方式找到指针指向的内存。
    • *p:表示这个指针p指向的该地址上存储的是多少。

57. 指针所占的内存空间

指针存的是一块地址,是一个16进制的数,占用4个字节(32位操作系统)。64位操作系统占8个。

58. 空指针

空指针:指向内存中编号为0的空间。

用途:初始化指针变量。

注意:空指针指向的的内存不可访问(null)。

定义int * p = NULL

c++中的空是全大写的NULL

示例代码:

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

void main() {
int a = 10;
int* p = NULL;

p = &a;
cout << "a的值是" << *p << endl;
cout << "a的地址值是" << p << endl;
system("pause");
}

59. 野指针

指针变量指向非法的内存空间。(指向没有申请的,非法的地址空间

示例代码:

1
2
3
4
5
6
7
8
9
#include<iostream>;
#include "function.h";
using namespace std;

void main() {
int * p = (int*)0x0011;

cout << *p << endl;
}

效果:

野指针异常

60. const修饰指针

const修饰指针的三种情况:

  • const修饰指针 — 常量指针:const int * p,指针指向的值(*代表值)不可以改,指针指向可以改
flowchart LR 1((pointer))-->a 1((pointer))-.->b style a fill:#f9f,stroke:#333,stroke-width:4px
  • const修饰常量 — 指针常量:int * const p,指针指向的值可以改,指针指向不可以改
flowchart LR 1((pointer))-->a 1((pointer))-.-xb style b fill:#f9f,stroke:#333,stroke-width:4px
  • const即修饰指针,又修饰常量,const int * const p。都不可以改。
flowchart LR 1((pointer))-->a 1((pointer))-.-xb style a fill:#f9f,stroke:#333,stroke-width:4px style b fill:#f9f,stroke:#333,stroke-width:4px

61. 使用指针访问数组

普通方式就像Java一样,for循环,然后遍历。

也可以通过指针,右移的方式遍历。

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

void main() {
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
// 普通遍历方式
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++) {
cout << arr[i] << endl;
}

cout << "--------------------------------" << endl;
// 指针遍历方式
int* p = arr;
for (int j = 0; j < (sizeof(arr) / sizeof(arr[0])); j++) {
cout << *p << endl;
p++;
}
}

62. 指针和函数

在之前的例子中,如果main函数的实参传入到某函数的形参中,形参的改变是不会改变实参。但是如果main函数的实参的指针传入到某函数作为形参,这个函数是可以通过地址改变实参的。

总结:想要改实参,用地址传递,不想改实参,用值传递。

63. 指针、数组、函数

案例描述:封装一个函数,利用冒泡排序,实现对这个数组的升序排列。

注意,c++不可以直接返回一个数组类型int[] 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
#include<iostream>;
#include "function.h";
using namespace std;

int* bubbleSort(int arr[], int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j + 1] < arr[j]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}

void printArray(int* p, int len) {
for (int i = 0; i < len; i++) {
cout << *p << endl;
p++;
}
}

int main() {
int arr[] = { 0,1,7,3,4,5,6,2,8,9 };
int len = sizeof(arr) / sizeof(arr[0]);
int* p = bubbleSort(arr, len);
printArray(p,len);
return 0;
}

指针可以用来记录一个数组的首地址。这是一个很重要的应用。

64. 结构体

结构体就是用户自定义的数据类型。(就像Java中自己定义一个类Student)

语法:struct 结构体名{结构体成员列表}

通过结构体创建变量的方式有三种:

  • struct 结构体名 变量名
  • struct 结构体名 变量名 = {成员1值, 成员2值}
  • 定义结构体时顺便创建变量

示例: 定义一个学生类型。

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

struct student {
string name;
int age;
};

int main(){
struct student s1;
s1.age = 21;
s1.name = "孙健耕";

cout << s1.age<<s1.name << endl;
}

其实结构体就是Java里面的那个标准类的青春版(没构造方法和set方法)。

而且,结构体本身不能直接打印。

65. 结构体数组

将自定义的结构体放入数组中方便维护。

语法:struct 结构体名 数组名[元素个数]={{},{},{}}

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;

struct student {
string name;
int age;
};

int main(){
struct student s1;
s1.age = 21;
s1.name = "孙健耕";
struct student s2;
s2.age = 24;
s2.name = "Janko";

struct student arr[2] = { s1 ,s2 };


cout << arr[0].age << arr[0].name << endl;
}

类似于Java。

66. 结构体指针

  • 利用操作符->可以通过结构体指针来访问结构体成员属性。

  • 创建对象时,struct可以省略。

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

struct student {
string name;
int age;
};

int main(){

student s1 = {"孙健耕",21};

student* p = &s1;

cout << "年龄是:" << p->age << ",姓名是:" << p->name << endl;


->本质上就是访问了成员变量的一种简写而已。

67. 结构体嵌套结构体

比如现在有一个学生结构体,包含姓名年龄,还有一个老师结构体,包含姓名年龄,还有这个老师带的学生结构体。

结构体可以嵌套。注意嵌套顺序。

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;

struct student {
string name;
int age;
};

struct teacher {
string name;
int age;
student person;
};


int main(){

student s1 = {"孙健耕",21};

teacher t1 = { "Sven",52,s1 };

student* p = &s1;
student* p2 = &t1.person;

cout << "年龄是:" << p->age << ",姓名是:" << p->name << endl;
cout << "年龄是:" << p2->age << ",姓名是:" << p2->name <<"他的学生名字是" <<p2->name<< endl;


}

68. 结构体做函数参数

比如,将学生传入一个参数中,打印学生身上所有的信息。

值传递:

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

struct student {
string name;
int age;
};

void printStudent(student person) {
cout << "姓名是" << person.name << ",年龄是" << person.age<<endl;
}

int main(){
student s1 = {"孙健耕",21};
printStudent(s1);
return 0;
}

地址传递:

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

struct student {
string name;
int age;
};

void printStudent(student* p) {
cout << "姓名是" << p->name << ",年龄是" << p->age<<endl;
}

int main(){
student s1 = {"孙健耕",21};
student* p = &s1;
printStudent(p);
return 0;
}

注意别搞混了,不能直接*p,因为这个是自定义的结构体。地址传递可以改变实参的。

69. 结构体中const使用场景

作用:使用const防止误操作。

如果数据样本很大,比如每个student结构体中有很多信息,而且student的数量很大,如果使用值传递,会占用很多的内存空间。这时可以使用地址传递(一个指针也就占4个字节),节约空间。但是值传递可能会有误修改影响实参的操作。这时候可以通过const来进行保护。

地址传递时的const保护,误操作会报错

70-71 结构体案例

设计一个英雄结构体,包含姓名,年龄,性别,然后用冒泡排序,按年龄升序排序并打印。

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;

struct hero {
string name;
int age;
string gender;
};

void printArr(hero* p, int len) {
for (int i = 0; i < len; i++) {
cout << "名字:\t" << p->name << "年龄是:\t" << p->age << "性别是:\t" << p->gender << endl;
p++;
}
}

hero* bubbleSort(hero heroArr[], int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (heroArr[j].age > heroArr[j + 1].age) {
hero temp = heroArr[j];
heroArr[j] = heroArr[j + 1];
heroArr[j + 1] = temp;
}
}
}
return heroArr;
}

int main(){
hero h1 = { "刘备",21,"男" };
hero h2 = { "关羽",22,"男" };
hero h3 = { "张飞",20,"男" };
hero h4 = { "赵云",21,"男" };
hero h5 = { "貂蝉",19,"女" };

hero heroArr[] = { h1,h2,h3,h4,h5 };
int len = sizeof(heroArr) / sizeof(heroArr[0]);
cout << "排序前打印" << endl;
for (int i = 0; i < len; i++) {
cout << "名字:\t" << heroArr[i].name << "年龄是:\t" << heroArr[i].age
<< "性别是:\t" << heroArr[i].gender << endl;
}
cout << "排序后打印" << endl;
printArr(bubbleSort(heroArr, len),len);
return 0;
}