Sign inSign up

C++ Notes

Philosophy_Mifan
Philosophy_Mifan
#1

笔记如有笔误,或者一些可补充的地方,请联系编者:E-mail:philosophy_mifan@outlook.com,GitHub:Philosophy-Mifan
注:各类标记如下:※->important(重点) ▲->familiar(熟识)

Chapter 0 开始

0 一切的开始“Hello world!” ※

#include <iostream>  //io表示input/output,stream表示数据流
/*namespace解决多人合作编写程序时出现的名字冲突问题*/
using namespace std;//使用系统标准的命名空间  std->standard

//main函数就是程序的入口和出口
int main()
{
	cout << "hello world!" << endl;//cout输出流对象,<<是流插入运算符,可以cout<<"data1"<<"data2"<<"data3",连续输入,endl->end of line 输出一个换行符号,且清空输出缓冲区,换行也可以使用\n
	return 0;//返回0,代表程序正常运行结束
}

想要进行输入/输出------>使用cin/cout------>需要包含<iostream>

注:在给变量命名时需注意,array、max、min这一类已经被C++内置使用的不得再次使用,否则会出现compile error

Chapter 1 数据类型

1.1 cin与cout详解 ※

在C++中,std命名空间是C++中标准库类型对象的命名空间。我们常用的输入和输出函数都是定义在std命名空间中的,因此,我们需要使用输入和输出时必须要引入std命名空间。

要引用一个命名空间中的内容,有三种方法,分别为使用using引入命名空间、使用using限定符和直接使用命名空间限制访问。

1.1.1 using引入命名空间

using namespace std;直接使用using完全引入std命名空间,后面可以直接使用std命名空间中的所有内容

1.1.2 使用using限定符

using std::cout; using std::cin;仅仅使用using引入了std命名空间中的cin和cout,接下来cin和cout都无需使用命名空间

1.1.3 显示使用

std::cout std::cin 直接使用std::的形式

1.1.4 cin

cin >> varname1 >> varname2 >> varname3;cin可以一次性输入任意多个变量的值

1.1.5 cout

cout << varname1 << varname2 << varname3;使用cout连续输出了三个变量
clog和cerr,分别用于输出log日志和删除错误信息,其语法与cout一模一样

1.2 常量与变量 ※

常量

固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量可以使任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。
常量就像是常规的变量,只不过常量的值在定义后不能进行修改。

变量

变量是程序运行中其值可以改变的量,变量需要制定数据类型,变量由变量名、变量值组成,常量直接使用即可;变量则先定义再使用。

1.3 常见数据类型 ※

整型integer --> int
字符型char --> char
浮点数 float --> float
布尔 bool --> bool,保存真ture,假false(true可以用非零整数代替,false可以用0代替)(C语言中的stdbool.h头文件包含)
变量定义:Elemtype varible_name = data;

  • 变量命名时
  1. 在名称中只能使用字母、数字和下划线_
  2. 名称的第一个字符不能是数字
  3. 区分大写字符和小写字符
  4. 不能将C++关键字用作名称
  • 其它内置类型
类型关键字
布尔型bool
字符型char
整型int
浮点型float
双浮点型double
无类型void
宽字符型wchar_t

1.4 各种类型字节数

类型位范围
char1个字节-128~127 or 0~255 //依据系统不同会有所差异,下同
unsigned char1个字节0~255
signed char1个字节-128~127
int4个字节-2147483648~2147483647
unsigned int4个字节0~4294967295
signed int4个字节-2147483648~2147483647
short int2个字节-32768~32767
unsigned short int2个字节0~65535
signed short int2个字节-32768~32767
long int8个字节-9,223,372,036,854,775,808~9,223,372,036,584,775,807
signed long int8个字节-9,223,372,036,854,775,808~9,223,372,036,584,775,807
unsigned long int8个字节0~18,446,744,073,709,551,615
float4个字节+/- 3.4e +/- 38(~7个数字)
double8个字节+/- 1.7e +/- 308(~15个数字)
long double16个字节+/- 1.7e +/- 308(~15个数字)
wchar_t2或4个字节1个宽字符

1.5 注释 ※

在必要时,可以将部分代码临时移除,是调试程序的好帮手。通常而言,合理(有效)的代码注释需要占到源代码的1/3左右

其注释方式与C语言一致,分为单行注释//和多行注释(也叫块注释)/* */

(注:在Windows系统下,Visual Studio 2022和CLion均可使用"Ctrl+Shift+/"组合快捷键打出块注释)

1.6 转义字符 ▲

在编程中有一些字符是无法打出的,转义可以理解为“采用某些方式暂时取消该字符本来的含义”,这里的“某种方式”指的就是在指定字符前添加反斜杠\,以此来表示对该字符进行转义。

1.7 数据的进制表示及相互转换 ※

1.7.1 进制

概念:进位计数制的简称,将数字符号按序排列成数位,并遵照某种由低位到高位的进位方式计数表示数值的方法

常见的几种进制

进制基数基本符号
二进制 (BIN)20.1
八进制 (OCT)80.1.2.3.4.5.6.7
十进制 (DEC)100.1.2.3.4.5.6.7.8.9
十六进制 (HEX)160.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F

1.7.2 基数和权

基数:某进制计数制允许选用的基本数字符号的个数称为基数
权:各进制中每一个固定位置对应的单位值称为权。权的大小是以基数为底,数字符号所处的位置的序号为指数的整数次幂。

$R^2$$R^1$$R^0$小数点$R^{-1}$$R^{-2}$
二进制$2^2=4$$2^1=2$$2^0=1$$2^{-1}=0.5$$2^{-2}=0.25$
八进制$8^2=64$$8^1=8$$8^0=1$$8^{-1}=0.125$$8^{-2}=0.015625$
十进制$10^2=100$$10^1=10$$10^0=1$$10^{-1}=0.1$$10^{-2}=0.01$
十六进制$16^2=256$$16^1=16$$16^0=1$$16^{-1}=0.0625$$16^{-2}=0.00390625$

1.7.3 进制间的相互转换

由于计算机内部使用的数字符号只有0和1,也就是二进制。二进制具有易于物理实现、运算简单、机器运行的可靠性高、以及通用性强的优点,所以必须有一种自动转换方法,在数据输入和输出计算机时,二进制和十进制能相互自动的转换。

在转换时,十进制转N进制使用“除基取余倒着读”或直接凑(如:十进制转二进制时,126=64+32+16+8+4+2,故为0111 1110)
N进制转十进制时,使用按权展开(见表1.7.2),故下列内容不做详细讲述

可访问[tool.oschina.net/hexconvert/]()进行自主练习

1.7.3.1 二进制与十进制互转(略)
1.7.3.2 八进制与十进制互转(略)
1.7.3.3 十进制与十六进制互转(略)
1.7.3.4 小数转换

这里只说明十进制转二进制的一个例子,其余与上述一致

如0.375$_{10}$ = ?$_2$

0.375 * 2 = 0.75 0 $\mid$
0.75 * 2 = 1.5 1 $\mid$ 此处要把整数部分取出丢掉
0.5 * 2 = 1 1 $\downharpoonright$
自上往下得,$0.011_2$

1.7.3.5 二、八、十六进制相互转换

八进制代表3个二进制,十六进制代表4个二进制,只需要对应起来即可(常用二进制与十六进制转换)

![](Picture/1.7 练习.jpg)

1.8 数据在内存中的存储方式 ※

1.8.0 前言

在计算机中,任何情况下都只能识别二进制,在底层存储数据的时候,一律存储的是“二进制的补码形式” ------> 补码形式效率最高

1.8.1 原码

以8位二进制数为例xxxx xxxx,最高位(第一位)代表符号位,0表示正数,1表示负数

1.8.2 反码

正数的反码就是其本身,但负数的反码需要将除符号位以外的所有位置全部取反
如:[x$_1$]原 = 01100110,其[x$_1$]反 = 01100110
[x$_2$]原 = 11100110,[x$_2$]反 = 10011001

1.8.3 补码

正数的补码还是其本身,但负数的补码需要将除符号位以外的所有位置全部取反,且最低位+1
如:[x$_1$]原 = 01100110,其[x$_1$]补 = 01100110
[x$_2$]原 = 01100110,其[x$_2$]补 = 10011010

1.8.4 计算机采用补码的原因

Q:为什么计算机要采用补码的方式来存储数据?采用原码不是更容易理解吗?

1.8.4.1 采用原码处理数据

假设计算机使用原码来处理数据,此时需要对$1-1$进行计算(要注意计算机不会减法,所以这里是$1+(-1)$)
从十进制的角度来计算,$1-1 = 0$,$1+(-1)=0$
但是从二进制原码的角度来计算,0000 0001+1000 0001 = 1000 0010 = -2 很明显是错误的结论

1.8.4.2 采用反码处理数据

同样的,以1.8.4.1的题为例,从二进制反码的角度进行计算
[0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
乍一看,输出的结果虽然正确了,但是反码对于0的定义就有+0和-0区分,也就是两种表示方式可以表示0,这种用两种方式去表达一个0虽然不会冲突,但并不会使得空间利用率最大化。

1.8.4.3 采用补码处理数据

以1.8.4.1的题为例,从二进制补码的角度进行计算
[0000 0001]补 + [1111 1111]补 = 1 [0000 0000]补 (1溢出,只保留8个低位) = [0000 0000]原 = 0
此时得到最优解,补码能唯一的表示0,同时也代表它比反码能够多表示一个数

注:对于无符号数和有符号数编码的定义,详见《深入理解计算机系统》(第3版) P43~P45

Philosophy_Mifan
Philosophy_Mifan OP
#2

Chapter 2 运算符与表达式

2.1 算术与赋值运算符

2.1.1 算术运算符 ※

运算符说明范例结果
+正号+33
-负号-4-4
+加法5 + 510
-减法10 - 55
*乘法5 * 210
/整除10 / 33
%取模10 % 31
++自增a = 2; a++3
--自减a = 2; a--1
#include <iostream>
//对"/"运算符进行验证
using namespace std;
int main()
{
	int a = 10, b = 3;//定义两个整型变量,且对其赋值
	int result = 0;//定义结果变量,对其初始化
	float result_f = 0.0;
	result = a / b;
	cout << result << endl;//输出结果,为3
	//为了让result输出小数,可以将一个变量改为浮点数,或者运算过程带浮点数
	result_f = a / (b * 1.0);
	cout << result_f << endl;//输出结果,为3.33333
    
    return 0;
}
//对“++”“--”的自增自减过程进行理解
#include <iostream>
using namespace std;
int main()
{
	int num = 100;
	cout << num++ << endl;	//此时输出结果为100,但num自增到了101
	cout << num << endl;	//根据上一个自增,此时输出101
	cout << num-- << endl;	//此时输出结果为101,但num自减到了100
	cout << num << endl;	//同理,此时输出100
	return 0;
}

2.1.2 赋值运算符 ※

运算符说明范例结果
=将一个表达式的值赋给另一个表达式C = A + BC = A + B
+=相加后再赋值C += AC = C + A
-=相减后再赋值C -= AC = C - A
*=相乘后再赋值C *= AC = C * A
/=相除后再赋值C /= AC = C / A
%=取余后再赋值C %= AC = C % A

2.1.3 比较运算符 ※

运算符说明范例结果
==相等4 == 3false , 0
!=不等于4 != 3true ,1
<小于4 < 3false , 0
>大于4 > 3true ,1
<=小于等于4 <= 3false , 0
>=大于等于4 >= 3true ,1

2.1.4 逻辑运算符 ※

运算符说明范例
&&逻辑与A && B
\\
!逻辑非!A

2.1.5 运算符优先级 ▲

优先级运算符结合律助记
1::从左至右作用域
2a++, a-- , type(), type{}, a(), a[], ->从左至右后缀自增减、函数风格转型<br />函数调用、下标、成员访问
3! , ~ , ++a , --a , +a , -a<br />(type), sizeof, &a , *a ,<br />new, new[], delete, delete[]从右至左逻辑非、按位非、前缀自增减、<br />正负、C风格转型、取大小、取址、<br />指针访问、动态内存分配
4.* , ->*从左至右指向成员指针
5a*b, a/b, a%b从左至右乘除、取模
6a+b , a-b从左至右加减
7<< , >>从左至右按位左右移
8< , <= , > , >=从左至右大小比较
9== , !=从左至右等价比较
10a&b从左至右按位与
11^从左至右按位异或
12`从左至右
13&&从左至右逻辑与
14\\
15a ? b : c,<br />=, +=, -=, *=, /=, %=, &=, ^=从右至左
16,从左至右逗号

2.2 数据类型转换 ※

自动类型转换也叫隐式转换

当不同类型的变量同时运算时就会发生数据类型的自动转换。

  • char和int两个类型的变量相加时,就会把char先转换成int再进行加法运算
  • 如果是int和double类型进行运算时,就会把int转换成double再进行运算
  • 条件判断中,非布尔型自动转换为布尔类型

强制转换也叫显式转换

  • (typeName) value (C语言的写法)
  • typeName (value) (C++的写法)
  • static_cast<>可以将值从一种数据类型转换为另外一种数据类型,格式为:static_cast<typeName>(value)
Philosophy_Mifan
Philosophy_Mifan OP
#3

Chapter 3 流程控制

3.1 三种流程控制

在程序中,程序运行的流程控制决定程序是如何执行的,主流的程序设计语言一般有三大流程控制语句:分别是顺序控制、分支控制和循环控制。
C++的常用流程控制有if、for、while、do while和switch。

顺序控制就是程序从上到下逐行的执行,中间没有任何判断和跳转。

3.2 if else elif switch语句※

3.2.1 if语句 ※

单独的if,也叫单分支结构。

语法:

if (condition)
{
	//do something
}

int num = 100;
if(num > 0)
{
	cout << "num > 0" << endl;
}

3.2.2 if-else语句 ※

双分支

语法:

if(condition)
{
	//do something_1
}
else
{
	//do something_2
}
int num2 == 1;
if(num2 == 1)
{
	cout << "num2 = 1" << endl;
}
else
{
	cout << "num2 != 1" << endl;
}

3.2.3 if-elif-elif-else 语句 ※

多分支

语法:

if(condition_1)
{
	//do something_1
}
else if(condition_2)
{
	//do something_2
}
else if(condition_3)
{
	//do something_3
}

int num;
if(num3 > 0)
{
	cout << "num3 > 0" << endl;
}
else if(num3 < 0)
{
	cout << "num3 < 0" << endl;
}
else if(num3 == 0)
{
	cout << "num3 = 0" << endl;
}

<img src="Picture/1.png" style="zoom:35%;" />

<img src="Picture/2.png" style="zoom:33%;" />

3.2.4 if嵌套语句 ※

在C++中,if语句、else if语句和else语句可以进行<u>任意</u>的嵌套。

语法

if (condition)
{
	if (condition1)
	{
		//do something1
	}
	else if (condition2)
	{
		//do something2
	}
}
else
{
	if(condition3)
	{
		//do something3
	}
	else if(condition4)
	{
		//do something4
	}
	else
	{
		//do something5
	}
}

int a = 1;
int b = 2;
if(a == 1)
{
    if (b == 2)
    {
		cout << "a等于1且a等于2" << endl;
    }
    else
    {
        cout << "b不等于1" << endl;
    }
}
else
{
    cout << "a不等于1" << endl;
}

3.2.5 switch语句 ※

switch语句从多个条件执行一个语句。它就类似于在C++中的多分支结构

语法

switch(n)
{
	case constant1:
		//如果n等于constant1,将执行的代码;
		break;
	case constant2:
		//如果n等于constant2,将执行的代码;
		break;
		.
		.
		.
	default:
		//如果n不配任何constant,将执行的代码
        break;
}

int n;
cin >> n;
switch(n)
{
    case 1:
        cout << 1 << endl;
    	break;
    case 2:
        cout << 2 << endl;
    	break;
    case 3:
        cout << 3 << endl;
    	break;
    default:
        cout << "不是1,2,3" << endl;
        break;
}

3.2.6 switch规则

  • switch语句中的expression必须是一个整型或枚举类型,或者是一个class类型,其中class有一个单一的转换函数将其转换为整型或枚举类型。
  • 在一个switch中可以有任意数量的case语句。每个case后跟一个要比较的值和一个冒号。
  • case的constant-expression必须与switch中的变量具有相同的数据类型,且必须是一个常量或字面量。
  • 当被测试的变量等于case中的常量时,case后跟的语句将被执行,直到遇到break语句为止。
  • 当遇到break语句时,switch终止,控制流将跳转到switch语句后的下一行。
  • 不是每一个case都需要包含break。如果case语句不包含break,控制流将会继续后续的case,直到遇到break为止。
  • 一个switch语句可以有一个可选的default case,出现在switch的结尾。default case可用于在上面所有case都不为真时执行一个任务。default case,出现在switch的结尾。default case中的break语句不是必需的。
3.2.6.1 案例

输入一个数字,检查等级10, 20, 30.

int n;
cin >> n;
switch(n)
{
	case 1:
		cout << 10 << endl;
		break;
	case 2:
		cout << 20 << endl;
		break;
	case 3:
		cout << 30 << endl;
		break;
}

3.3 条件表达式(三目运算符) ▲

C/C++条件表达式使用三目运算符?:完成,适当条件下可与if else语句相互替换。
条件表达式优点在于可直接返回表达式运算的结果。

语法:条件表达式?值1:值2; //如条件表达式为ture,返回值1,否则,返回值2

3.3.1 案例

求两个数的差,结果总是为大的数减去小的数

int a, b, c;
cin >> a >> b;
c = (a > b) ? a - b : b - a;
cout << c << endl;

3.4 while循环 ※

有的时候,可能需要多次执行同一块代码。
编程语言提供了允许更为复杂的执行路径的多种控制结构。
循环语句允许多次执行一个语句或语句组。

3.4.1 while语句

C++的while循环跟if和for语句类似,都是使用{}作为分隔符,要执行的代码块都是相同的缩进形式。

语法

while (condition)
{
	//do something
}

案例

循环打印0-5的数字

int num = 0;
while(num <= 5)
{
	cout << "num=" << endl;
	num++;
}

3.4.2 do-while循环

C++的do while循环跟while循环类似,不过,do while循环与while循环的区别是不管do while的条件是否为真,do while至少会循环一次。

语法

do{
	//do something
}while(condition)

<img src="Picture/3.png" style="zoom:60%;" />

案例
int i = 1;
do{
	cout << i++ << endl;
}while(i <= 10);

3.5 for循环 ※

3.5.1 for语句

for循环用于重复程序的一部分几次(或多次)。如果迭代次数是固定的,建议使用for循环,其次是while和do while循环。

语法

for (express1;express2;express3)
{
	//statements
}

<img src="Picture/4.png" style="zoom:50%;" />

3.5.1.1 案例1

用for循环打印从1到10

for (int i = 1; i <= 10; i++)
{
	cout << i << endl;
}
3.5.1.2 案例2

实现输出以下情况的代码
<img src="Picture/5.png" style="zoom:67%;" />

for (int i = 0; i < 3; i++) {
		cout << "\ni = " << i << endl;
		for (int j = 0; j < 5; j++) {
			cout << "\tj = " << j;		//\t是制表符等同于Tab的效果
		}
	}
	cout << "\nOver" << endl;

3.6 break和continue

break:语句出现时将立即终止循环
continue:

break案例:从1开始打印,遇到第一个能同时被3和4整除的数字时立即终止

int num = 1;
while(1){
		if(num % 3 == 0 && num % 4 ==0){
		break;
		}
num++;
}

continue案例:打印1-10之间的偶数

for (int i = 0; i <= 10; i++){
if (i % 2 == 0){
continue;
}
cout << i << endl;
}

do while案例:找出三位数内所有的水仙花数(指一个三位数,它每一位数字的三次幂之和等于它本身)

3.7 随机数

rand和srand

C++中没有自带的random函数,要实现随机数的生成就需要使用rand()和srand(),且这两个函数需要头文件<cstdlib>

rand()函数---->伪随机数

rand()会返回一随机数值,范围在0~RAND_MAX之间,RAND_MAX定义在stdlib.h中。

案例:产生一个0~99的随机数

#include<iostream>
#include<cstdlib>
using namespace std;
int main(){
cout << rand()%100;
return 0;
}

srand (种子)

srand()课用来设置rand()产生随机数时的随机数种子。通过设置不同的种子,我们可以获取不同的随机数序列。

可以利用srand((int)(time(NULL)) (或将NULL改为0)的方法,利用系统时钟,产生不同的随机数种子。需要调用time()函数,此函数被包含在头文件<ctime>中。

time()获取的值是从1970.1.1 00:00:00开始到现在的秒数

案例:输出10个随机数

#include<iostream>
#include<cstdlib>
#include<ctime>
#define random(x) rand()%(x)
using namespace std;
int main(){
		srand((int)time(0));//产生随机种子,NULL == 0
		for (int i = 0; i < 10 ; i++){
		cout << rand() % 100 << " "; //也可以用宏定义使得函数的使用更方便和直观
		cout << random(100) << " ";//与上行输出完全一致
		}
return 0;
}

其他的随机数的范围通式

要取得[0,n)的随机整数,就是rand()%n,表示从0~n-1的数

要取得[a,b)的随机整数,使用(rand() % (b - a)) + a;

要取得[a,b]的随机整数,使用(rand() % (b - a + 1)) + a;

要取得(a,b]的随机整数,使用(rand() % (b - a)) + a + 1;

通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。

要取得a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)。

要取得0~1之间的浮点数,可以使用rand() / double(RAND_MAX)。

案例:利用随机数产生一个[0,1)的一位小数

srand(time(NULL));
cout << rand() / double (RAND_MAX) << endl;

作业:输出一个1~100的随机整数

cout << rand()%100 + 1;即可

综合案例1——猜数字游戏

Part I 在1~100之间取个数字,然后输入数字进行猜测,直到猜对了为止

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main(){
    //随机一个1~100之间的整数
    srand((unsigned)time(NULL));
    int guess = rand() % 100 + 1;    
    //输入数字
    int num = 0;
    //不确定多少次能猜对,使用无限循环
    while(1){
        cout << "请输入数字(1-100): " << endl;
        cin >> num;
        if (num == guess) {
            cout << "恭喜" << endl;
            break;
        }
    }
    return 0;
}

注:以下代码均不带头文件 + std命名空间使用 + main()函数框架(包括return 0;)

Part II 在I的基础上,提示猜数字的范围,若小,提示小,若大,提示大,猜对直接退出

while(1){
  cout << "请输入数字(1-100): " << endl;
  cin >> num;
  if (num == guess){
     cout << "恭喜" << endl;
     break;
  }
  else if (num < guess){
      cout << "猜小了" << endl;
  }
  else{
      cout << "猜大了" << endl;
	}
}

Part III 加一个难度选择(如普通模式下拥有10次机会,中等为5次,困难为2次)

//选择等级
    int level = 0;
    char c = 0;
    cout << "选择等级:(容易a 10次机会;中等b 5次机会;困难c 2次机会)" << endl;
    cin >> c;
    if (c == 'a'){
        level = 10;
    }
    else if (c == 'b'){
        level = 5;
    }
    else if (c == 'c'){
        level = 2;
    }
    //输入数字
    int num = 0;
    int i = 1;
    //不确定多少次能猜对,使用无限循环
    for (; i <= level;i++){
        cout << "请输入数字(1-100): " << endl;
        cin >> num;
        if (num == guess)
        {
            cout << "恭喜" << endl;
            break;
        }
        else if (num < guess){
            cout << "猜小了" << endl;
        }
        else{
            cout << "猜大了" << endl;
        }
    }
    if (i > level){
        cout << "失败了,你猜的次数已经达到上限了" << endl;

Part IV 加一个死循环,能让玩家选择是否重复游玩

#include<iostream>
using namespace std;
int main() {
	//随机一个1~100之间的整数
	while (1) {
		srand((unsigned)time(NULL));
		int guess = rand() % 100 + 1;
		//选择等级
		int level = 0;
		char c = 0;
		cout << "选择等级:(容易a 10次机会;中等b 5次机会;困难c 2次机会)" << endl;
		cin >> c;
		if (c == 'a') {
			level = 10;
		}
		else if (c == 'b') {
			level = 5;
		}
		else if (c == 'c') {
			level = 2;
		}
		else {
			cout << "无效输入,退出游戏!" << endl;
		}
		//输入数字
		int num = 0;
		int i = 1;
		//不确定多少次能猜对,使用无限循环
		for (; i <= level; i++) {
			cout << "请输入数字(1-100): " << endl;
			cin >> num;
			if (num == guess)
			{
				cout << "恭喜" << endl;
				break;
			}
			else if (num < guess) {
				cout << "猜小了" << endl;
			}
			else {
				cout << "猜大了" << endl;
			}
		}
		if (i > level) {
			cout << "失败了,你猜的次数已经达到上限了" << ' ' << guess << endl;
		}
		cout << "你是否要继续游玩Y/N" << endl;
		cin >> c;
		if (c == 'N') {
			break;
		}
		else if (c == 'Y') {
			continue;
		}
		else {
			cout << "无效输入,退出游戏!" << endl;
			break;
		}
	}
	return 0;
}
Philosophy_Mifan
Philosophy_Mifan OP
#4

Chapter 4 数组

4.1 一维数组 ※

前言

在程序开发的过程中,很多场景,我们需要保存很多的数据,或者说我们需要保存一组数据,使用普通的datatype不能满足我们的需求,C++为开发者提供了数组(array)用来保存一组数据。

在C++中,数组保存的一组数据的类型必须是完全一致的,C++不支持数组保存不同的数据类型。

4.1.1 一维数组的定义和初始化

定义:

datatype array[length];------>int arr_num[5];

int arr_num[5] = {1, 2, 3, 4, 5}; //初始化

4.1.2 数组名要求

1.只能由字数字和下划线组合,但不能数字开头; 2.系统关键字不能作为数组名; 3.同一作用域下不能重名; 4.见其名知其意。

4.1.3 数组下标

数组是一组具有连续内存位置的类似类型元素。

在C++中std::array是一个封装固定大小数组的容器,数组索引从0开始,可以在C++数组中只存储固定的元素集合。

4.1.4 一维数组的数据元素访问

访问数组的元素,使用中括号,即[]加上数组索引即可,要注意,下标从0开始。

因此,如果我们需要访问数组的第一个元素,其索引应该为0,要访问数组的最后一个元素,其索引是数组的长度减1.

数组名[索引/下标]

a. 直接通过下标进行访问

b. 循环访问

int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++){
	cout << arr[i] << " ";
}

c. 使用foreach循环遍历

int arr[5] = {1, 2, 3, 4, 5};
for (int i:arr)//:之后是容器的变量名,这里指多个值的容器且连续的,int一般可以用auto让编译器自行判断变量类型
{
		cout << i << " ";//这里的i不再是下标,而是数据本身
}

d. 循环赋值

int arr[5];
for (int i = 0; i < 5; i++){
  	cin >> arr[i];
}

4.1.5 练习1

现有五只小猪,体重分别为764,942,613,476,940。定义一个一维数组,存放五只小猪的体重,找出其中最重的小猪,并输出出来。

#include <iostream>
#include <cstdlib>

using namespace std;

int arr[5]
int m;
int main(){
  	for (int i = 0; i < 5; i++){
      	cin >> arr[i];
    }
    m = arr[0];
    for (int i = 1; i < 5; i++){
        if (m < arr[i]){
            m = arr[i];
        }
    }
    cout << m;
}

4.1.6 练习2

输入n(n <= 100)个数保存到数组中,求n个数的和以及平均值

#include <iostream>
#include <cstdlib>

using namespace std;

int arr[103];
int n, sum;
int main(){
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    for (int i = 0; i < n; i++){
        sum += arr[i];
    }
    cout << sum << ' ' << sum / n << endl;
}

比较两个数组是否相等,不能直接使用比较运算符 == ,需要比较数组的长度和数组中的元素是否一致。

#include <iostream>

using namespace std;

int arr1[5],arr2[5];

int main(){
    for (int i = 0; i < 5; i++){
        cin >> arr1[i];
    }
    for (int i = 0; i < 5; i++){
        cin >> arr2[i];
    }
    //比较两个数组的元素个数是否一致,使用sizeof()函数
    int arr1Count = sizeof(arr1) / sizeof(arr1[0]);//用数组的长度除去单个元素的长度,就代表数组中元素的个数
    int arr2Count = sizeof(arr2) / sizeof(arr2[0]);
    if (arr1Count != arr2Count)//数组内元素个数不同这两个数组绝对不相同
    {
        cout << "arr1 != arr2" << endl;
        return 0;
    }
    //判断数组各元素是否一一对应一致(元素数据相同,但位置不同也不是相同的数组)
    for (int i = 0; i < arr1Count; i++){
        if (arr1[i] != arr2[i]){
            cout << "arr1 != arr2" << endl;
            return 0;
        }
    }
    cout << "arr1 == arr2" << endl;
}

4.2 冒泡排序 ※

在上述数组是否相同的比较中,提到了元素数据相同,但位置不同也不是相同的数组;接下来只需要满足元素相同即可,不需要满足位置也一致。此时就需要使用排序先将各数组的元素先有序排序后在一一比较。

4.2.1 冒泡排序的步骤

1.比较相邻的元素。如果第一个元素比第二个元素大,就交换他们两个。

2.对每一对相邻元素做同样的工作,执行完毕后,找到第一个最大值。

3.重复以上的步骤,每次比较次数-1,直到不需要比较。

附:第一趟排序完以后,末尾必定是最大元素。

如:8、7、6、5、4进行冒泡排序,第一趟过程如下图所示

![](file:///F:/documents/repo/cs-Notes/Language/Cpp/Picture/6.png?lastModify=1789891323)

4.2.2 交换数据

现有int a = 1; int b = 2; 输出:a = 2 b = 1

A.临时变量法

int a = 1, b = 2, tmp = 0;
tmp = a;
a = b;
b = tmp;//通过一个tmp变量来保证互换时的数据不丢失

B.加减法

int a = 1, b = 2;
a = a + b;//a = 3
b = a - b;//b = 1
a = a - b;//a = 2

C.系统函数调用(swap)

swap(x,y);//将x,y两个值互换

4.2.3 冒泡排序的实现

int arr[5] = {3, 1, 2, 4, 5};
for (int i = 0; i < 5 - 1;i++){//比较趟数
  	for (int j = 0; j < 5 - i -1; j++){//比较次数
      	if (arr[i] > arr[j+1]){
          	swap(arr[j], arr[j + 1]);
        }
    }
}
for (int i = 0; i < n; i++){
  	cout << arr[i] << " ";
}

4.3 普通查找 ※

根据值顺序查找

比如在给定数组中查找固定的某个值,就可以从下标为0的元素开始顺序对比查找。

案例

在长度为N(N <= 100)的数组中,输入m个元素,查找k元素第一次出现的位置(位置从1开始),如果找不到k,输出-1.

input: 5 1 3 5 6 3 3

output: 2

#include <iostream>

using namespace std;

int arr[105];
int m, k;

int main(){
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> arr[i];
    }
    cin >> k;
    for (int i = 1; i <= m; i++){
        if (k == arr[i]){
            cout << i << endl;
            return 0;
        }
        return -1;
    }
    return 0;
}

4.4 二分查找 ※

二分查找也叫折半查找(Binary Search),是一种效率较高的查找方法。

  1. 定义两个变量l和r;//left值和right值
  2. 用mid(猜的数)保存l和r的中间值;
  3. 假如小了,则把l变成mid + 1(将左半部分排除在外);
  4. 假如大了,则把r变成mid - 1(将右半部分排除在外);
  5. 假如正确,return mid;
  6. 假如 l > r,则返回-1;

注:用于查找的内容逻辑上来说是需要有序的;查找的数量只能是一个,而不是多个。

过程如下图所示

案例1

在数组长度为N(N <= 100)的数组中,输入m个元素(有序),查找k元素的位置(位置从1开始),如果找不到k,输出-1.

input: 5 11 22 33 44 55 22

output: 2

#include <iostream>

using namespace std;

int arr[105], m, k;
int main(){
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> arr[i];
    }
    cin >> k;
    //定义左下标、右下标、中间标号
    int left = 0,right = m,mid = 0;
    while (left < right){
        mid = (left + right)/2;
        if (arr[mid] == k){
            cout << mid << endl;
            break;
        }
        else if (arr[mid] < k){//所找数据比中间值大,必然在右边,将左标移到中间+1的位置
            left  = mid + 1;
        }
        else{//所找数据比中间值小,必然在左边,将左标移到中间-1的位置
            left = mid - 1;
        }
    }

    if (left > right)//当左标大于右标时,说明该元素无法被找到,返回-1
    {
        cout << -1 << endl;
    }
    return 0;
}

案例2

如案例1所示,一个有序数组中有多个重复的元素,输出这个元素第一次出现的位置(从1开始)

input: 10 11 22 22 22 22 22 22 33 44 55 22

output: 2

#include <iostream>

using namespace std;

int arr[105], m, k;
int main(){
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> arr[i];
    }
    cin >> k;
    //定义左下标、右下标、中间标号
    int left = 0,right = m,mid = 0;
    while (left < right){
        mid = (left + right)/2;
        if (arr[mid] == k){
            //往mid的左边继续查找是否一样
            while(arr[mid - 1] == k && mid - 1 >= 0){
                mid--;
            }
            cout << mid << endl;
            break;
        }
        else if (arr[mid] < k){//所找数据比中间值大,必然在右边,将左标移到中间+1的位置
            left  = mid + 1;
        }
        else{//所找数据比中间值小,必然在左边,将左标移到中间-1的位置
            left = mid - 1;
        }
    }

    if (left > right)//当左标大于右标时,说明该元素无法被找到,返回-1
    {
        cout << -1 << endl;
    }
    return 0;
}

案例3

如案例1所示,一个有序数组中有多个重复的元素,输出这个元素最后一次出现的位置(从1开始)

input: 10 11 22 22 22 22 22 22 33 44 55 22

output: 7

#include <iostream>

using namespace std;

int arr[105], m, k;
int main(){
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> arr[i];
    }
    cin >> k;
    //定义左下标、右下标、中间标号
    int left = 0,right = m,mid = 0;
    while (left < right){
        mid = (left + right)/2;
        if (arr[mid] == k){
            //往mid的右边继续查找是否一样
            while(arr[mid + 1] == k && mid + 1 <= m){
                mid++;
            }
            cout << mid << endl;
            break;
        }
        else if (arr[mid] < k){//所找数据比中间值大,必然在右边,将左标移到中间+1的位置
            left  = mid + 1;
        }
        else{//所找数据比中间值小,必然在左边,将左标移到中间-1的位置
            left = mid - 1;
        }
    }

    if (left > right)//当左标大于右标时,说明该元素无法被找到,返回-1
    {
        cout << -1 << endl;
    }
    return 0;
}

案例4

查找元素个数

在一个有序数组中,相同元素一定是挨着的,只需要找到这个元素,在左右寻找,记录个数即可。

在元素个数N(N <= 100)的数组中,输入m个元素(有序),查找k元素的个数,如果找不到,输出0.

input: 10 11 22 22 22 22 22 22 33 44 55 22

output: 6

1#include <iostream>
2​
3using namespace std;
4​
5int arr[105], m, k, cnt;
6int main(){
7    cin >> m;
8    for (int i = 1; i <= m; i++){
9        cin >> arr[i];
10    }
11    cin >> k;
12    //定义左下标、右下标、中间标号
13    int left = 0,right = m,mid = 0;
14    while (left < right){
15        mid = (left + right)/2;
16        if (arr[mid] == k){
17            cnt++;
18            int left_index = mid - 1;
19            int right_index = mid + 1;
20            //往mid的左边继续查找是否一样
21            while(arr[left_index] == k && left_index >= 0){
22                left_index--;
23                cnt++;
24            }
25            //往mid的右边继续查找是否一样
26            while(arr[right_index] == k && right_index <= m){
27                right_index++;
28                cnt++;
29            }
30            break;
31        }
32        else if (arr[mid] < k){//所找数据比中间值大,必然在右边,将左标移到中间+1的位置
33            left  = mid + 1;
34        }
35        else{//所找数据比中间值小,必然在左边,将左标移到中间-1的位置
36            left = mid - 1;
37        }
38    }
39    cout << cnt << endl;
40    
41    return 0;
42}

4.5 一维数组修改元素 ※

1.通过下标直接修改 2.先查找,再修改

案例1

蛋糕厂有两台包装称重机编号为01与02,以01号包装一块、02号包装一块、01号包装一块,以这样的方式轮流工作。经检查今日02好机器出现故障,每一块小蛋糕都少称m克,已知今日包装了n块小蛋糕,输入修正前n块小蛋糕的重量,输出修正后每块蛋糕的质量。

input:

三行,第一行一个整数n(0 < n <= 10000),第二行为n个浮点数表示修正前小蛋糕的质量,第三行为一个浮点数m表示因机器故障导致每块小蛋糕缺少的质量。 5 50.5 48.2 50.5 47.7 50.2 3.2

output: 50.5 51.4 50.5 50.9 50.2

#include <iostream>

using namespace std;

float arr[10005], m;
int n;

int main(){
    cin >> n;
    for (int i = 0; i < n;i++){
        cin >> arr[i];
    }
    cin >> m;
    for (int i = 0; i < n; i++){
        if(i % 2 == 1){
            arr[i] += m;
        }
        cout << arr[i] << " ";
    }
    return 0;
}

案例2

在一次100m百米跑比赛中,有n(5 <= n <= 10)个同学进行比赛,输入这n个同学的比赛成绩,把第一名和最后一名的同学的位置进行交换,再输出比赛成绩。

input: 5 10.3 9.8 10.6 9.9 11

output: 10.3 11 10.6 9.9 9.8

#include <iostream>

using namespace std;

float arr[12];
int n;
int max_index, min_index;
int main(){
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    max_index = 0;
    min_index = 0;
    for (int i = 1; i < n; i++){
        max_index = (arr[max_index] < arr[i]) ? i : max_index;
        min_index = (arr[min_index] > arr[i]) ? i : min_index;
    }
    swap(arr[max_index],arr[min_index]);
    for (int i = 0; i < n; i++){
        cout << arr[i] << " ";
    }
    return 0;
}

4.6 数组元素的添加 ※

4.6.1 添加到数组末尾

根据现有的最大下标+1,直接插入

注意:插入的下标一定小于等于数组长度减1

案例

马路前方塞车,目前塞车数量n,输入n辆车的编号,后来又有m辆加入塞车队伍,输出现在所有塞车车辆编号,总车辆(n+m <= 100).

input: 3 12 15 9 5 10 11 8 19 6

output: 12 15 9 10 11 8 19 6

#include <iostream>

using namespace std;

int n, m, arr[102];

int main(){
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    cin >> m;
    for (int i = 0; i < m; i++){
        cin >> arr[i + n];
    }
    for (int i = 0; i < n + m; i++){
        cout << arr[i] << " ";
    }
    return 0;
}

4.6.2 根据要求添加到数组

案例

班里转来一位新同学,班主任老师安排这位同学坐在小张这一组,输出安排新同学座位后,小张所在小组每位同学的学号

input:

共4行,第一行为一个整数n(0 < n < 60)代表小张同学这一组的同学人数,第二行为n个整数代表小张同学所在小组每位学生的学号,第三行为一个整数k代表新来同学的学号,第四行两个整数p, m,若p = 1,则新同学坐在第m个同学的前面,若p = 0,则新同学坐在第m个同学的后面。 5 7 1 3 14 5 56 0 2

output:

一行,新同学安排好后,小张所在小组每位同学的学号 7 1 56 3 14 5

#include <iostream>

using namespace std;

int n, arr[62], k, p, m, tmp;

int main(){
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> arr[i];
    }
    cin >> k;
    cin >> p >> m;
    tmp = n;
    if (p == 0){
        for( ; n > m; n--){
            arr[n+1] = arr[n];
        }
        arr[m+1] = k;
    }
    else{
        for ( ; n >= m; n--){
            arr[n+1] = arr[n];
        }
        arr[m] = k;
    }
    for (int i = 1; i <= tmp + 1; i++){
        cout << arr[i] << " ";
    }
    return 0;
}

4.7 一维数组删除元素 ※

案例

小赵在录入n(2 <= n <= 100)个同学的分数,该数组中有个数据是严重错误的分数,找出该分数,删掉后,打印其余正常分数。

input: 5 90 98 87 -100 89

output: 90 98 87 89

#include <iostream>

using namespace std;

int point[105];
int n, tmp;

int main(){
    cin >> n;
    for(int i = 0; i < n - tmp; i++){
        cin >> point[i];
        if (point[i] < 0){
            i--;
            tmp++;
        }
    }
    for (int i = 0; i < n - tmp;i++){
        cout << point[i] << " ";
    }
}

4.8 一维数组元素合并 ※

4.8.1 无序合并

案例

给定两个数组arr1和arr2,数组长度不超过100,合并成arr3

input: 4 11 2 13 41 3 100 200 300

output: 11 2 13 41 100 200 300

#include <iostream>

using namespace std;

int arr1[105], arr2[105], arr3[205];
int n1,n2;

int main(){
    cin >> n1;
    for(int i = 0; i < n1; i++){
        cin >> arr1[i];
    }
    cin >> n2;
    for(int i = 0; i < n2; i++){
        cin >> arr2[i];
    }
    for(int i = 0; i < n1; i++){
        arr3[i] = arr1[i];
    }
    for(int i = 0; i < n2; i++){
        arr3[i+n1] = arr2[i];
    }
    for(int i = 0; i < n1+n2; i++){
        cout << arr3[i] << " ";
    }
    return 0;
}

4.8.2 有序合并

要求合并后按降序或升序输出结果

案例

输入两个升序数组num1与num2,将num1与num2合并到num3中,并且删除重复的数,使得num3依旧为一个升序数组(不能先合并后排序)

input: 四行,第一行,先输入一个整数N1(0 < N1 < 100)代表num1数组的元素个数,再输入N1个整数代表num1中的N1个元素值,下两行同理 6 -3 2 5 7 8 12 8 0 1 2 3 10 13 18 20

input: -3 0 1 2 3 5 7 8 10 12 13 18 20

#include <iostream>

using namespace std;

int num1[105], num2[105], num3[205];
int N1, N2;

int main(){
    cin >> N1;
    for(int i = 0; i < N1; i++){
        cin >> num1[i];
    }
    cin >> N2;
    for(int i = 0; i < N2; i++){
        cin >> num2[i];
    }
    int index = 0, i = 0, j = 0;
    for(;i < N1 && j < N2;){
        if (num1[i] < num2[j]){
            num3[index++] = num1[i++];
        }
        else if(num1[i] > num2[j]){
            num3[index++] = num2[j++];
        }
        else{
            num3[index++] = num1[i++];
            j++;
        }
    }
    while (i < N1){
            num3[index++] = num1[i++];
        }
    while (j < N2){
            num3[index++] = num2[j++];
        }
    for(int i = 0; i < index; i++){
            cout << num3[i] << " ";
        }
    return 0;
}

练习:定义一个int型的一维数组,包含10个元素,分别赋值为1~10,然后将数组中的元素都向前移一个位置,即a[0] = a[1], a[1] = a[2]...最后一个元素的值是原来第一个元素的值,然后输出这个数组

input: 1 2 3 4 5 6 7 8 9 10

output: 2 3 4 5 6 7 8 9 10 1

#include <iostream>

using namespace std;

int arr[10];

int main(){
    for(int i = 0; i < 10; i++){
        cin >> arr[i];
    }
    int temp = arr[0];
    for(int i = 1; i < 10; i++){
        arr[i-1] = arr[i];
    }
    arr[9] = temp;
    for(int i = 0; i < 10; i++){
        cout << arr[i] << " ";
    }
    return 0;
}

4.9 字符数组 ※

4.9.1 字符数组定义与声明 ▲

C++中的字符数组,是用来存放一连串字符的数组,C++的字符数组使用与其他数组基本相同,但初始化时,可以直接使用字符串进行初始化,但初始化时,可以直接使用字符串进行初始化,也就是C语言风格的字符串

语法

char arrName[count]; char arrName[count] = "abcde"; char arrName[count] = {'a','b','c','d','e'}; char arrName[count] = {"abcde"};

4.9.2 字符和字符串的区别

char是字符,也是0~127的无符号整数。通常能用一个char表示的被称为ASCII编码。

字符串是以NULL结尾的连续地址。日常生活中常用转义字符 \0 表示(如下图所示)

4.9.3 字符数组的地址 ▲

cout在遇到char数组时,会输出整个字符串,直到遇到\0,所以就算是普通的char单个字符地址,我们也需要(void*)来强制转换其类型

注:void被翻译为无类型,void*为无类型指针,一般称为通用指针或泛指针。

4.9.4 字符数组的输入和输出 ※

cin >>遇到 ' '和'\n'都会停止输入 gets(arrc) //能够直接读取到'\n' puts(arrc) //打印一串字符直到'\n'

4.9.5 字符数组的操作 ※

在使用字符数组的操作时,需添加从C语言移植的头文件#include<cstring>

#include <cstring>
strlen(p); 		//取字符串长度
strcpy(p, p1); 	//复制字符串
strncpy(p, p1, n)	//复制指定长度的字符串
strcat(p, p1)	//附加字符串
strncat(p, p1, n)	//附加指定长度的字符串
...

4.9.5.1 strlen(p)

p代表字符数组名,通用格式写为strlen(array_name); 其作用是返回字符数组中的元素个数,且'\0'不被包含在内。

char arr[10] = 'abc';
int len = strlen(arr);
cout << len << endl;		//输出为3

4.9.5.2 strcpy(p1, p2)

将p2复制到p1,再返回p1的值

char arr1[10] = "abc";
char arr2[10] = "jkl";
//arr1 = arr2;  不可取,这是一个非法的指令
strcpy(arr1, arr2);		//在VS22中不能使用该函数,被认为unsafe,使用strcpy_s即可
cout << arr1 << endl;	//输出为jkl

4.9.5.3 strncpy(p1, p2, n)

将p2前n个字符复制到p1,返回p1的值

char arr1[10] = "abc";
char arr2[10] = "jkl";
strncpy(arr1, arr2, 2);	//在VS22中不能使用该函数,被认为unsafe,使用strncpy_s即可
cout << arr1 << endl;	//输出为jk

4.9.5.4 strcmp(p1, p2)

通过ASCII码来比较p1和p2的大小,若p1 > p2 → return 1; p1 = p2 → return 0; p1 < p2 → return -1; 注:strcmp是对一个字符一个字符进行比较,而非整体进行对比,详情看下面样例中的arr5、arr6对比

char arr1[10] = "abc";
char arr2[10] = "abc";
char arr3[10] = "abb";
char arr4[10] = "abd";
int a = strcmp(arr1, arr2);
int b = strcmp(arr1, arr3);
int c = strcmp(arr1, arr4);
cout << a << endl; 	//输出为0
cout << b << endl;	//输出为1
cout << c << endl;	//输出为-1
char arr5[10] = "ab";
char arr6[10] = "ba";
int d = strcmp(arr5, arr6);
cout << d << endl;	//输出为-1,从第一个字符a的ASCII值小于b的值时就已停止比较

4.9.5.5 strncmp(p1, p2, n)

通过ASCII码来比较p1、p2的大小,对前n个字符进行比较。

char arr1[10] = "abc";
char arr2[10] = "abb";
int length = 2;		//设定需要比较的字符串长度
int a = strncmp(arr1, arr2, length);
cout << a << endl;	//输出为0

4.9.5.6 stricmp/strcasecmp

对于不同的操作系统分成两个函数,stricmp用于Windows平台,strcasecmp则用于MacOS/Linux系统,不区分大小写比较字符串1和字符串2的大小,其结果由函数返回值带回。

但是现在对于stricmp函数为过时的,需要使用ISO C标准,可用_stricmp函数代替

char c1[10] = "ABCDE";
char c2[10] = "abcde";
int a = _stricmp(c1, c2);
cout << a << endl;		//输出为0

//在不相等时,输出为差值
char c3[10] = "aBCDE";
char c4[10] = "Zbcde";
int b = _stricmp(c3, c4);
cout << b << endl;		//输出为-25

4.9.5.7 strnicmp

不区分大小写比较字符串1和字符串2指定位数的大小。

char c1[10] = "abc";
char c2[10] = "abd";
int a = _strnicmp(c1, c2, 2);//函数前加下划线理由同stricmp
cout << a << endl;		//输出为0

4.9.5.8 strcat(p1, p2)

将p2拼接到p1后(返回p1的值)

char c1[100] = "abcde";
char c2[100] = "fghij";
strcat_s(c1, c2);
cout << c1 << endl;		//输出为abcdefghij

4.9.5.9 strncat

将字符串2的前n个字符连接到字符串1后边(返回字符串1的值)

char c1[100] = "abcde";
char c2[100] = "fghij";
strncat_s(c1, c2, 3);
cout << c1 << endl;		//输出为abcdefgh

4.9.5.10 strchr/strrchr

strchr(char_array name, search for letter)在字符串中查找指定字符 strrchr(char_array name, search for letter)在字符串中反向查找指定字符

返回一个指向该字符串中第一次出现该字符的指针,如果字符串中不包含该字符则返回NULL空指针。

char c[100] = "aBCDBE";
cout << strchr(c, 'B') << endl;
//输出结果为BCDBE,具体使用返回指针详见指针部分
cout << strrchr(c, 'B') << endl;
//输出结果为BE

4.9.5.11 strlwr/strupr

strlwr(char_array name)将字符串中的大写字母换成小写字母 strupr(char_array name)将字符串中的小写字母换成大写字母

在现在使用中,此函数被认为不安全,需替换成_strlwr/strupr_s

char c[100] = "asdJKSHjd";
_strlwr_s(c);
cout << c << endl;		//asdjkshjd
_strupr_s(c);
cout << c << endl;		//ASDJKSHJD

4.9.5.12 memset

memset(char_array name, initial value, initial bytes)

将某一块内存中的内容全部设置为指定的值

char c[10];
memset(c, 'a', sizeof(c));
for(int i = 0; i < 10; i++){
	cout << c[i] << ' ';
}
//
int arr[10];
memset(arr, 12, sizeof(arr));	//将12更改为0,输出则为正确,也就相当于清空内存是被允许的。
for(int i = 0; i < 10; i++){
	cout << arr[i] << ' ';		//输出arr是不正确的访问,在memset里是默认一个字节处理,而整型占4个字节,无法处理
}

4.9.6 字符串到数值类型的转换 ※

atoi(p)字符串转换到int整型;atof(p)字符串转换到double浮点数;atol(p)字符串转换到long整型

char c[100] = "131.343";
int n = atoi(c);
cout << n << endl;		//输出为131
double n1 = atof(c);
cout << n1 << endl; 	//输出为131.343
long n2 = atol(c);
cout << n2 << endl;		//输出为131

4.9.7 字符检查 ※

#include<cctype>		//以下函数需要包含此头文件
isalpha(); 		//检查是否为字母字符
isupper(); 		//检查是否为大写字母字符
islower();		//检查是否为小写字母字符
isdigit();		//检查是否为数字
isxdigit();		//检查是否为十六进制数字表示的有效字符
isspace();		//检查是否为空给类型字符
iscntrl();		//检查是否为控制类字符
ispunct();		//检查是否为标点符号
isalnum();		//检查是否为字母和数字
isprint();		//检查是否是可打印字符
isgraph();		//检查是否是图形字符,等效于 isalpha() || ispunct()
char c = 'A';
char a = '5';
cout << isdigit(a) << endl;		//输出一个非零数即代表True
cout << isupper(c) << endl;		//同上
//其余不再展示

4.9.8 练习

统计数字字符个数,输入一行字符,统计出其中数字字符的个数。 input: dream abc jlje 2022. 要求其总长度<255 output: 4 输出字符串里面数字字符的个数

#include <iostream>
#include <cstring>
using namespace std;
char c[260];
int cnt, n;
int main() {
	gets_s(c);
	n = strlen(c);
	for (int i = 0; i < n; i++) {
        //下部分使用isdigit函数
		if (isdigit(c[i])) {
			cnt++;
		}
         //下部分使用ASCII码对比
        if (c[i] >= 48 && c[i] <= 57){
            cnt++;
        }
	}
	cout << cnt << endl;
	return 0;
}

给定一个只包含小写字母的字符串,请你找到第一个仅出现一次的字符。如果没有,输出no input format: 一个字符串,length < 100000 output format: 输出第一个仅出现一次的字符,没有输出no input: abcabd output: c

int main() {
	cin >> c;
	n = strlen(c);
	for (int i = 0; i < n; i++) {
		a[c[i] - 97]++;
	}
	for (int i = 0; i < n; i++)
	{
		if (a[c[i] - 97] == 1) {
			cout << c[i] << endl;
			return 0;
		}
	}
	cout << "no" << endl;
	return 0;
}

4.10 字符串 string类

4.10.1 C++中的string类 ▲

C++标准库提供了string类类型,支持4.9 字符数组的所有操作,并且还增加了其他更多的功能。

1#include<iostream>
2#include<string>
3using namespace std;
4int main(){
5    string str1 = "Hello";
6    string str2 = "World";
7    string str3;
8
9    //复制str1到str3
10    str3 = str1;
11    cout << "str3 :" << str3 << endl;
12
13    //连接str1和str2
14    str3 = str1 + str2;
15    cout << "str1 + str2 :" << str3 << endl;
16
17    //连接后,str3的长度
18    int len = str3.size();
19    cout << str3.size() << len << endl;
20
21    return 0;
22}

4.10.2 string类几种常见的构造函数

C++中提供了专门的头文件string,来支持string类型。string类定义隐藏了字符串的数组性质,让我们可以像处理普通变量那样处理字符串。string对象和字符数组之间的主要区别是:可以将string对象声明为简单变量,而不是数组。

string(const char *s):将string对象初始化为s指向的字符串 string(size_type n, char c):创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c string(const string &str):将一个string对象初始化为string对象str(复制构造函数) string():创建一个默认的string对象,长度为0(默认构造函数)

string类的设计允许程序自动处理string的大小,上述第四个代码创建了一个长度为0的对象,但是向str中写入数据时,程序会自动调整str的长度、因此,与使用数组相比,使用string对象更方便,也更安全

4.10.3 string的常见操作 ※

4.10.3.1 获取string对象的长度

在C语言中,使用strlen函数获取字符串的长度。在C++中,可以使用string.size()函数或string.length()函数来获得string对象的长度。这两个函数的实现完全一致,并没有区别,length()方法是C原因习惯保留的,而size()方法则是为了兼容STL容器而引入的。

string s = "abcde";
int len = s.size();
cout << len << endl;

4.10.3.2 复制string对象

在C语言中,使用strcpy,strncpy函数来实现字符串的复制。在C++中则方便很多,可以直接将一个string对象赋值给另一个string对象

string str1 = "Hello World!"
string str2 = str1;
cout << str1 << ' ' << str2 << endl;

无须担心长度溢出的情况,string会自行调整大小

4.10.3.3 string对象的拼接和附加

在C语言中,使用strcat、strncat函数来进行字符串拼接操作。在C++中可以使用“+”和“+=”实现拼接和附加操作。 1、使用 + 操作符拼接两个字符串

string str1("hello");
string str2("world!");
string str3 = str1 + str2;
cout << str3 << endl;

2、使用 += 操作符在字符串后面附加内容 可以使用 += 来在一个string对象后面附加一个string对象、字符以及C风格的字符串

string str1("hello");
string str2("world!\n");
str1 += str2;
str1 += "nice job\n";
str1 += 'a';
cout << str1 << endl;

3、使用string.append()函数 可以使用string.append()函数来在一个string对象后面附加一个string对象或C风格的字符串

string str1 = "hello,world!";
string str2 = "HELLO,WORLD!";

str1.append(str2);
cout << str1 <<endl;
str1.append("C string");
cout << str1 << endl;

4、使用string.push_back()函数 可以使string.push_back()函数来在一个string对象后面附加一个字符。

string str("Hello");
str.push_back("h");		//添加一个字符到str字符串后
cout << str << endl;

4.10.3.4 string对象的对比

在C语言中,使用strcmp、strncmp函数来进行字符串的比较。在C++中,由于将string对象声明为了简单变量,直接使用关系运算符(==、!=、<、<=、>、>=)即可

string str1("hello");
string str2("hello");

if (str1 == str2){
	cout << "str1 = str2" << endl;
}
else if(str1 < str2){
	cout << "str1 < str2" << endl;
}
else{
	cout << "str1 > str2" << endl;
}

也可以使用类似strcmp的函数进行string对象的比较,string类提供的是string.compare()方法,函数原型如下(多态,详见OOP(面向对象编程)) int compare(const string& str) const; int compare(size_t pos, size_t len, const string& str) const; int compare(size_t pos, size_t len, const string& str, size_t subpos, size_t sublen) const; int compare(const char* s) const; int compare(size_t pos, size_t len, const char* s) const; int compare(size_t pos, size_t len, const char* s, size_t n) const;

compare方法的返回值如下: 1)返回0,表示相等; 2)返回结果小于0,表示比较字符串中第一个不匹配的字符比源字符串小,或者所有字符都匹配但是比较字符串比源字符串短; 3)返回结果大于0,表示比较字符串中第一个不匹配的字符比源字符串大,或者所有字符都匹配但是比较字符串比源字符串长。

string str1("aBcdef");
string str2("AbcdEf");
string str3("123456");
string str4("123dfg");
//下面是各种比较方法
int m = str1.compare(str2);	//完整的1和2进行比较
int n = str1.compare(1, 5, str2);	//"Bcdef"和"AbcdEf"比较
int p = str1.compare(1, 5, str2, 4, 2);	//"Bcdef"和"Ef"比较
int q = str3.compare(0, 3, str4, 0, 3);	//"123"和"123"比较
cout << m << ' ' << n << ' ' << p << ' ' << q << endl;	//输出+ + - 0

4.3.10.5 string对象的查找操作

1、使用string.find()方法查找字符,其中npos代表-1,也就是不存在的位置

find方法的函数原型如下:

  1. 从字符串的pos位置开始(若找不到pos的值,则默认从索引0开始),查找子字符串str。如果找到,则返回该子字符串首次出现时其首字符的索引;否则,返回string::npos size_type find(const string& str, size_type pos = 0) const;
  2. 从字符串的pos位置开始(若不指定pos 的值,则默认从索引0处开始),查找子字符串s。如果找到,则返回该字符串首次出现时其首字符的索引;否则,返回string::npos size_type find(const char *s, size_type pos = 0) const;
  3. 从字符串的pos位置开始(若不指定pos的值,则默认从索引0处开始),查找s的前n个字符组成的子字符串。如果找到,则返回该子字符串首次出现时起首字符的索引;否则,返回string::npos size_type find(const char *s, size_type pos, size_type n);
  4. 从字符串的pos位置开始(若不指定pos的值,则默认从索引0处开始),查找字符ch。如果找到,则返回该字符首次出现的位置;否则,返回string::npos size_type find(char ch, size_type pos = 0)const;
string str("cat,dog,cat,pig,little cat,hotdog,little pig,angry dog");
size_t catPos = str.find("cat",0);

if(catPos == string::npos){
	printf("没有找到字符串\n");
	return 0;
}

while(casPos != string::npos){
	cout << "在索引" << catPos << "处找到字符串" <<endl;
	catPos = str.find("cat", catPos + 1);
}

//以下为4种函数原型的用法
string s = "abc123 ghfhg";
int index = s1.find("123");	//1、2两种函数原型
cout << index << endl;		//输出3

int n = s.find("123", 0, 2);
cout << n << endl;		//输出3

string s1 = "abc12sda1123dfjsw";
int n1 = s1.find("123", 0, 2);	//也就是寻找"12"字符串
cout << n1 <<endl;		//输出3

int n2 = s1.find('c');
cout << n2 << endl;		//输出2

从上述发现size_type pos参数不输入默认为0,string.rfind()只是查找顺序相反,也就是找到其出现的最后一次位置,这里就不再赘述。

2、string.find_first_of()

string.find_first_of()方法在字符串中从指定位置开始向后(默认从索引0处)查找参数中任何一个字符首次出现的位置。

string str("catlsfhiwaknf");
int pos = str.find_first_of("gat");
if(pos == string::npos){
	printf("没有匹配到\n");
	return 0;
}
else{
	cout << "在索引" << pos << "处匹配到" << endl;
	//程序输出的结果是:在索引1处匹配到
}

所查找的字符串gat中,第一次出现字符串str中的字符是'a',该字符在str中的索引是1.

3、string.find_last_of()

在字符串中查找参数中任何一个字符最后一次出现的位置(也就是从指定位置开始往前查找,第一个出现的位置)。

4、string.find_first_not_of()

在字符串中查找参数中第一个不包含在参数中的位置

5、string.find_last_not_of()

在字符串中查找参数中最后一个不包含在参数中的位置

string str = "abkd12c kdjf";
cout << str.find_first_of("jkn") << endl;
cout << str.find_last_of("jkn") << endl;
cout << str.find_first_not_of("jkn") << endl;
cout << str.find_last_not_of("jkn") << endl;

4.3.10.6 使用string.substr()函数来获取子串

string substr(size_t pos = 0, size_t len = npos)const; 其中,pos是子字符串的起始位置(索引,第一个字符的索引为0),len是子串的长度。这个函数的功能是:复制一个string对象中从pos处开始的len个字符到string对象substr中去,并返回str。

string str("Hello,World!");
string subStr = str.substr(3, 5);
cout << subStr << endl;		//输出结果为:"lo,Wo".

4.3.10.7 访问string字符串的元素

可以像C语言中一样,将string对象当做一个数组,然后使用数组小标的方式来访问字符串中的元素;也可以使用string.at(index)的方式来访问元素(索引号从0开始)

string str("Hello,World!");
cout << str[1] << endl;		//使用数组下标的方式访问string字符串的元素
cout << str.at(1) << endl;	//使用at索引访问string字符串的元素

4.3.10.8 string对象的插入操作

使用string.insert()进行插入操作,函数原型如下 string &insert(size_t pos, const string &str); //在位置pos处插入字符串str string &insert(size_t pos, const string &str, size_t subpos, size_t sublen); //在位置pos处插入字符串str的从位置subpos处开始的sublen个字符 string &insert(size_t pos, const char* s) //在位置pos处插入字符串s; string &insert(size_t pos, const char* s, size_t n) //在位置pos处插入字符串s的前n个字符 string &insert(size_t pos, size_t n, char c); //在位置pos处插入n个字符c iterator insert(const_iterator p, size_t n, char c); //在p处插入n个字符c,并返回插入后迭代器的位置 iterator insert(const_iterator p, char c); //在p处插入字符c,并返回插入后迭代器的位置

其中const_iterator代表直接的位置,也就是用str.begin() //也就是从位置0插入

begin / end详见迭代器。

4.11 案例——点餐系统

1#include <iostream>
2​
3using namespace std;
4​
5int main() {
6    cout << "-----------------自助点餐系统-----------------" << endl;
7    cout << "菜单:" << endl;
8    cout << "1火锅 2回锅肉 3宫保鸡丁 4夫妻肺片 5毛血旺 6麻婆豆腐 7东坡肘子 8水煮牛肉" << endl;
9    cout << "9龙抄手 10钟水饺 11串串香 12肥肠粉 13担担面" << endl;
10    cout << "14可乐 15雪碧 16脉动 17果缤纷 18果粒橙 19营养快线" << endl;
11    cout << "套餐A(优惠10元)∶火锅 回锅肉 东坡肘子,任选其一" << endl;
12    cout << "龙抄手 钟水饺 串串香,任选其一" << endl;
13    cout << "可乐雪碧,任选其一" << endl;
14    cout << "套餐B(优惠12元)︰火锅 回锅肉 东坡肘子 水煮牛肉,任选其一" << endl;
15    cout << "肥肠粉 担担面,任选其一" << endl;
16    cout << "脉动 果缤纷 果粒橙,任选其一" << endl;
17    cout << "本店消费满100元,可加1元,换购一种饮料" << endl;
18​
19    //选择单点 套餐 结账菜单变量
20    char menu;
21    int index;//接收编号
22​
23    //菜名
24    string name[19] = { "火锅","回锅肉","宫保鸡丁","夫妻肺片","毛血旺","麻婆豆腐","东坡肘子","水煮牛肉","龙抄手","钟水饺","串串香","肥肠粉","担担面","可乐","雪碧","脉动","果缤纷","果粒橙","营养快线" };
25​
26    //价格数组
27    int price[19] = { 30,20,15,45,21,34,22,23,31,16,18,19,11,9,13,10,9,8,6 };
28    //数量数组
29    int cnt1[19], cnt2[19];
30    memset(cnt1, 0, 19 * sizeof(int));
31    memset(cnt2, 0, 19 * sizeof(int));
32    //总价格、总优惠
33    int total = 0, offer = 0;
34​
35    while (true) {
36        cout << "A单点 B套餐 C结账" << endl;
37        cin >> menu;
38        if (menu == 'A') {
39            cout << "输入编号点餐,输入0结束点餐" << endl;
40            while (1) {
41                cin >> index;
42                if (index == 0) {
43                    break;
44                }
45                switch (index) {
46                case 1:
47                    cnt1[0]++;
48                    break;
49                case 2:
50                    cnt1[1]++;
51                    break;
52                case 3:
53                    cnt1[2]++;
54                    break;
55                case 4:
56                    cnt1[3]++;
57                    break;
58                case 5:
59                    cnt1[4]++;
60                    break;
61                case 6:
62                    cnt1[5]++;
63                    break;
64                case 7:
65                    cnt1[6]++;
66                    break;
67                case 8:
68                    cnt1[7]++;
69                    break;
70                case 9:
71                    cnt1[8]++;
72                    break;
73                case 10:
74                    cnt1[9]++;
75                    break;
76                case 11:
77                    cnt1[10]++;
78                    break;
79                case 12:
80                    cnt1[11]++;
81                    break;
82                case 13:
83                    cnt1[12]++;
84                    break;
85                case 14:
86                    cnt1[13]++;
87                    break;
88                case 15:
89                    cnt1[14]++;
90                    break;
91                case 16:
92                    cnt1[15]++;
93                    break;
94                case 17:
95                    cnt1[16]++;
96                    break;
97                case 18:
98                    cnt1[17]++;
99                    break;
100                case 19:
101                    cnt1[18]++;
102                    break;
103                }
104            }
105​
106            total = 0;
107            for (int i = 0; i < 19; i++) {
108                if (cnt1[i] != 0) {
109                    cout << name[i] << price[i] << "元,数量:" << cnt1[i] << " 小计:" << price[i] * cnt1[i] << "元" << endl;
110                    total += price[i] * cnt1[i];
111                }
112            }
113            cout << "总计:" << total << "元,优惠:" << offer << "元" << endl;
114        }
115        else if (menu == 'B') {
116            while (true)
117            {
118                cout << "套餐A  套餐B (输入字母确定)" << endl;
119                cin >> menu;
120                if (menu == 'A') {
121                    //套餐A
122                    cout << "1火锅 2回锅肉 7东坡肘子" << endl;
123                    cin >> index;
124                    switch (index) {
125                    case 1:
126                        cnt2[0]++;
127                        break;
128                    case 2:
129                        cnt2[1]++;
130                        break;
131                    case 7:
132                        cnt2[6]++;
133                        break;
134                    }
135                    cout << "9龙抄手 10钟水饺 11串串香" << endl;
136                    cin >> index;
137                    switch (index) {
138                    case 9:
139                        cnt2[8]++;
140                        break;
141                    case 10:
142                        cnt2[9]++;
143                        break;
144                    case 11:
145                        cnt2[10]++;
146                        break;
147                    }
148                    cout << "14可乐 15雪碧" << endl;
149                    cin >> index;
150                    switch (index) {
151                    case 14:
152                        cnt2[13]++;
153                        break;
154                    case 15:
155                        cnt2[14]++;
156                        break;
157                    }
158                    break;
159                }
160                else if (menu == 'B') {
161                    //套餐B
162                    cout << "1火锅 2回锅肉 7东坡肘子 8水煮牛肉" << endl;
163                    cin >> index;
164                    switch (index) {
165                    case 1:
166                        cnt2[0]++;
167                        break;
168                    case 2:
169                        cnt2[1]++;
170                        break;
171                    case 7:
172                        cnt2[6]++;
173                        break;
174                    case 8:
175                        cnt2[7]++;
176                        break;
177                    }
178                    cout << "12肥肠粉 13担担面" << endl;
179                    cin >> index;
180                    switch (index) {
181                    case 12:
182                        cnt2[11]++;
183                        break;
184                    case 13:
185                        cnt2[12]++;
186                        break;
187                    }
188                    cout << "16脉动 17果缤纷 18果粒橙" << endl;
189                    cin >> index;
190                    switch (index) {
191                    case 16:
192                        cnt2[15]++;
193                        break;
194                    case 17:
195                        cnt2[16]++;
196                        break;
197                    case 18:
198                        cnt2[17]++;
199                        break;
200                    }
201                    break;
202                }
203                else {
204                    cout << "请输入正确的大写字母!!" << endl;
205                }
206            }
207            total = 0;  //  清零是为了去掉之前点过单的值
208            for (int i = 0; i < 19; i++) {
209                if (cnt2[i] != 0) {
210                    cout << name[i] << price[i] << "元,数量:" << cnt2[i] << " 小计:" << price[i] * cnt2[i] << "元" << endl;
211                    total += price[i] * cnt2[i];
212                }
213            }
214            cout << "总计:" << total << "元,优惠:" << offer << "元" << endl;
215        }
216        else if (menu == 'C') {
217            //结账
218            if (total >= 100) {
219                cout << "总消费超过100元,请问是否加1元换购饮料(Y/N)" << endl;
220                cin >> menu;
221                if (menu == 'Y') {
222                    total += 1;
223                    cout << "14可乐 15雪碧 16脉动 17果缤纷 18果粒橙 19营养快线" << endl;
224                    cin >> index;
225                    switch (index)
226                    {
227                    case 14:
228                        offer += price[13] - 1;
229                        break;
230                    case 15:
231                        offer += price[14] - 1;
232                        break;
233                    case 16:
234                        offer += price[15] - 1;
235                        break;
236                    case 17:
237                        offer += price[16] - 1;
238                        break;
239                    case 18:
240                        offer += price[17] - 1;
241                        break;
242                    case 19:
243                        offer += price[18] - 1;
244                        break;
245                    }
246                }
247            }
248            cout << "您总共消费:" << total << "元,优惠了" << offer << "元" << endl;
249            break;
250        }
251        else {
252            cout << "请输入正确的大写字母!!" << endl;
253        }
254    }
255    cout << "--------------------欢迎下次光临--------------------" << endl;
256​
257    return 0;
258}

4.12 二维数组 ※

4.12.1 概述

C++的数组是支持多维的,如果是二维数组,则数组中的每一个元素都是一个一维数组;如果是三维数组,那么每一个元素都是一个二维数组。

4.12.2 二维数组的声明与定义

C++的二维数组,也就是数组中的每一个元素都是一个一维数组。 type arrayName[row][col] //数据类型 数组名[行数][列数]

4.12.3 二维数组的初始化

1、可在声明之后再赋值进行初始化

2、声明时就进行初始化 int arrnum[2][2] = {{1,2},{3,4}} //推荐写法,可快速分清所表达数组

4.12.4 练习

output:{{1,0,0,0,0,1},{0,1,0,0,1,0},{0,0,1,1,0,0},{0,0,1,1,0,0},{0,1,0,0,1,0},{1,0,0,0,0,1}}

#include <iostream>
using namespace std;
int main(){
    int arr[6][6];
    for (int i = 0; i < 6; i++){
        for(int j = 0; j < 6; j++){
            if(i == j || i + j == 6){
				arr[i][j] = 1;
            }
            else{
                arr[i][j] = 0;
            }
        }
    }
    return 0;
}

4.12.5 二维字符数组

二维字符数组其实跟其他类型数组差不多,在初始化时可以被简化,可以看成多个一维字符数组组成了二位字符数组。

char arr[3][10] = {"Hello","world","C++"}; //每个字符串占一行 char arr[][10] = {"Hello","world","C++"} //同理,第二维的值不能被省略

4.12.6 练习

用1~100的随机数填充一个n行m列的整型数组(n和m均小于100),输出每行每列的和

input:(其中{1,2,3,4,5},{5,2,3,4,1},{6,7,8,1,2}为随机数生成,并非手动输入) 3 5 1 2 3 4 5 5 2 3 4 1 6 7 8 1 2

output: 15 15 24 12 11 14 9 8

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int arr[100][100];
int row[100];
int col[100];
int main() {
	int n, m, total = 0;
	cin >> n >> m;
	srand((unsigned)time(0));
	//产生随机数
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			arr[i][j] = rand() % 100 + 1;
		}
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			row[i] += arr[i][j];
			col[j] += arr[i][j];
		}
	}
	for (int i = 0; i < n; i++) {
		cout << row[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < m; i++) {
		cout << col[i] << " ";
	}
	return 0;
}

计算矩阵边缘之和:输入一个整数矩阵,计算位于矩阵边缘的元素之和。所谓矩阵边缘的元素,就是第一行和最后一行的元素以及第一列和最后一列的元素,输入第一行分别为矩阵的行数m和列数n(m < 100, n < 100),两者之间以一个空格分开。 接下来输入的m行数据中,每行包含n个整数,整数之间以一个空格分开。输出对应矩阵的边缘元素之和

input: 3 3 3 4 1 3 7 1 2 0 1

output: 15

1#include <iostream>
2using namespace std;
3int arr[100][100];
4int main() {
5    int n, m, total = 0;
6    cin >> n >> m;
7    for (int i = 0; i < n; i++) {
8        for (int j = 0; j < m; j++) {
9            cin >> arr[i][j];
10        }
11    }
12    for (int i = 0; i < n; i++) {
13        for (int j = 0; j < m; j++) {
14            if (i == 0 || j == 0 || i == n - 1 || j == m - 1) {
15                total += arr[i][j];
16            }
17        }
18    }
19    cout << total << endl;
20    return 0;
21}

对字符串中的所有单词进行倒排。构成单词的字符只有26个大写或小写英文字母,单词与单词间用空格隔开,要求倒排后的单词间隔符以一个空格表示,每个单词最长20个字母。 数据范围:字符串长度不超过100 input: this is c plus plus output:plus plus c is this

#include <iostream>
#include <cstring>
using namespace std;
char c[100][20];
int main() {
	int i = 1;
	while (cin >> c[i++]);
	i--;
	while (--i >= 1) {
		cout << c[i] << " ";
	}
	return 0;
}

4.13 应用——迷宫

用o表示人物,通过键盘控制人物通过迷宫入口走到迷宫出口,输入wasd来进行↑←↓→操作

1#include <iostream>
2using namespace std;
3​
4char map[10][21] = {
5    {"##################o#"},
6    {"#    ############# #"},
7    {"# ## ###########   #"},
8    {"# ##          ## ###"},
9    {"# #########        #"},
10    {"# ##################"},
11    {"# ##      ##########"},
12    {"#            #######"},
13    {"############      ##"},
14    {"################# ##"}
15};
16​
17​
18void showMap();
19void showMenu();
20char direction; //存储接收到的方向字符
21void move();
22bool isSuccess();
23​
24bool gameover = 0;//游戏结束的标记
25int currentpr = 0, currentpc = 18;
26int newpr = currentpr, newpc = currentpc;
27​
28int main() {
29​
30    while (!gameover) {
31        system("cls");//清屏
32        showMap();
33        showMenu();
34        if (isSuccess()) {
35            cout << "恭喜你,通关了!" << endl;
36            return 0;
37        }
38        cin >> direction;
39        switch (direction)
40        {
41        case 'w':
42            newpr--;
43            break;
44        case 'a':
45            newpc--;
46            break;
47        case 's':
48            newpr++;
49            break;
50        case 'd':
51            newpc++;
52            break;
53        case 'q':
54            cout << "你确定要退出吗?(Y/N)";
55            cin >> direction;
56            if (direction == 'Y') {
57                gameover = 1;
58            }
59        }
60        move();
61    }
62    return 0;
63}
64​
65void showMap() {
66    for (int i = 0; i < 10; i++) {
67        cout << map[i] << endl;
68    }
69}
70​
71void showMenu() {
72    cout << "↑:w  ←:a  ↓:s  →:d  exit:q" << endl;
73    cout << "请输入方向字符:" << endl;
74}
75​
76void move() {
77    if (map[newpr][newpc] == ' ') {
78        map[newpr][newpc] = 'o';
79        map[currentpr][currentpc] = ' ';
80        currentpr = newpr;
81        currentpc = newpc;
82    }
83    //当撞墙时,回退到移动前的位置
84    else {
85        newpr = currentpr;
86        newpc = currentpc;
87    }
88}
89​
90bool isSuccess() {
91    if (map[9][17] == 'o') {
92        return 1;
93    }
94    else {
95        return 0;
96    }
97}
Philosophy_Mifan
Philosophy_Mifan OP
#5

Chapter 5 函数

5.1 概述

C++中的函数在其他变成语言中也称为过程或子例程。我们可以创建函数来执行任何任务,一个函数可以被调用多次,函数提供模块化和代码可重用性。

函数有很多的优点,其中提高代码的可重用性和代码优化是较为重要的两部分。 1、提高代码的可重用性:通过在C++中创建函数,可以调用函数多次。所以事先相同的功能不需要重复写一段相同的代码,只需要调用一个函数就可以了(这个过程也被称为封装) 2、代码优化:函数能使代码优化,理由同上。

5.2 函数类型

C++中有两种类型的函数: 1、库函数:是在C++头文件中声明的函数,如:ceil(x)、cos(x)等 2、用户定义的函数:是由C++程序员创建的函数,以便可以多次使用它。它降低了大程序的复杂性并优化了代码。

定义语法

 type funcName(paramlist){
     //todo
     return value;   //or not
 }

声明语法:type funcName(paramlist);

直接定义函数一般放在main()函数之前,定义完即可使用,优先考虑在main()函数之前声明,将函数放在main()之后定义。

所定义函数没有返回值时,type为void即可

定义一个函数change(),传入一个int类型的参数 a,在函数体中,对该函数的参数实现变成2被的操作,调用该函数并传入变量 x,执行完函数之后,再次打印变量 x 的值,发现其值并没有发生改变,还是传入时的值。这是因为在C++中,函数的参数是形式参数,也就是实参的拷贝,修改的是拷贝纸而非实参,故x的值并不会被改变。

5.3 引用 ※

引用即为某个已存在的变量取别名,引用变量与被引用变量公用一块内存空间。通过在数据类型后、变量名前添加"&"符号(取地址运算符,在这里可读做引用)来定义引用类型。 type &name = data;

 int num1 = 1;
 int &num2 = num;    //num2就是num的别名,也是num的引用
  • 引用只是别名,不占内存;和它引用的变量公用同一块内存空间;
  • 引用仅在定义时带&,使用时和普通变量一样使用即可;
  • 引用必须在创建时被初始化,一旦引用被初始化后,就不能改变引用所指向的变量,类似于指针常量
  • 引用必须与一个确定的合法内存单元相关联,不存在NULL引用且不可以使用

注:一个变量可以有多个引用。

1、做参数 void change(int &v1, int &n2);

2、引用作为返回值 type& funcName(); 当函数返回一个引用时,则返回一个指向返回值的隐式指针。 当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

形参:函数括号里面的变量参数,叫做形式参数 type funcName(type v1, type v2); 实参:调用函数时给的实值,叫做实际参数 funcName(2, 3);

5.4 函数可变参数 ▲

函数可变参数 type funcName(paramType1 param1, ...) 可变参数,即函数参数的个数是任意的,最典型的可变参数就是系统内置的scanf函数和printf函数。 可变参数的函数必须至少有一个强制参数,可选参数的类型可以变化。可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。 对于每一个强制参数来说,函数头部都会显示一个适当的参数,像普通函数声明一样。参数列表的格式是强制性参数在前,后面跟着一个逗号和省略号(...),这个省略号代表可选参数

可变参数函数要获取可选参数时,必须通过一个类型为va_list的对象,它包含了参数信息。这种类型的对象也称为参数指针(argument pointer),它包含了栈中至少一个参数的位置。 可以使用这个参数指针从一个可选参数移动到下一个可选参数,由此,函数就可以获取所有的可选参数。va_list这个类型被定义在头文件cstrarg中(C语言的stdarg.h)。 当编写支持参数数量可变的函数时,必须用va_list类型定义参数指针,以获取可选参数。当我们处理可变参数时,主要涉及到以下几个宏函数

va_start:coid va_start(va_list argptr, lastparam); argptr //定义好的va_list变量 lastparam //强制参数

宏va_start使用第一个可选参数的位置来初始化argptr参数指针。该宏的第二个参数必须是该函数最后一个有名称参数的名称。必须先调用该宏,才可以开始使用可选参数

va_arg:type va_arg(va_list argptr, type); argptr //定义好的va_List变量 type //可变参数的具体类型

展开宏va_arg会得到当前argptr所引用的可选参数,也会讲argptr移动到列表中的下一个参数。宏va_arg的第二个参数是刚刚被读入的参数的类型。

va_end:void va_end(va_list argptr); argptr //定义好的va_list变量

当不再需要使用参数指针时,必须调用宏va_end。如果想使用宏va_start或者宏va_copy来重新初始化一个之前用过的参数指针,也必须先调用宏va_end。

5.4.1 案例

用函数可变参数,实现求任意变量的和。

#include <cstdio>
#include <cstdarg>	//使用宏va_list/va_start/va_end
#include <iostream>

using namespace std;

//n代表有多少个参数会进入这个函数,...为省略的参数
int mulSum(int n, ...) {
	int sum = 0;
	va_list argptr;	//声明一个对象
	va_start(argptr, n);
	//对每个可选参数,其读取类型为int
	for (int i = 0; i < n; i++) {
		sum += va_arg(argptr, int);	//累加到sum中
	}
	va_end(argptr);
	return sum;
}

int main() {
	int result = mulSum(4, 1, 3, 5, 8);	//这里的第一个4代表后续有四个参数
	cout << "result = " << result << endl;
	return 0;
}

注:在VS中可能会出现没有va_start或va_end,就改成 __crt_va_start和 __crt_va_end。

5.5 函数默认参数 ▲

定义函数时,可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值(没有对应的实参),那么就会使用这个默认值。默认参数指的是当函数调用中省略了实参时自动使用的一个值,这个值就是给形参指定的默认值。

void add(int v1, int v2 = 100){		//这里v2就是函数默认参数
    cout << v1 + v2 << endl;
}
int main(){
    add(1);	//输出101
    add(1, 2);	//输出3
}

5.6 函数返回值 ※

C++中函数可以不返回任何值,也可以返回一个值,但C++的函数不支持返回多个值。同时,C++函数的返回值需要显式的声明其类型。 return value;

输入两个整数v1,v2,交换v1和v2的值。

void swap(int &v1, int &v2){
	int v3 = v1;
	v1 = v2;
	v2 = v3;
}

注:需要使用引用才能使得修改是实际的

5.7 函数嵌套调用 ▲

函数可以嵌套调用,即可以在一个函数里面调用另一个函数

函数嵌套调用,实现求阶乘的和(从1开始)

#include <iostream>

using namespace std;

int factorial(int num);
int factorialSum(int key);

int main() {
	int Result = factorialSum(10);
	cout << "Result = " << Result << endl;
	return 0;
}

//实现每一位数的阶乘
int factorial(int num) {
    if (num <= 1) return 1;          // 递归终止条件
    return num * factorial(num - 1); // 函数内部嵌套调用自身
}

//将每一位数的阶乘相加
int factorialSum(int key) {
	int sum = 0;
	for (int i = 1; i <= key; i++) {
		sum += factorial(i);
	}
	return sum;
}

5.8 递归 ※

函数递归也就是一个函数在函数体内又调用了自己本身,这个过程被称之为递归。

对于递归的理解,对于n次的递归,将前n-1次看做一个整体,第n次只是简单的操作;在n-1次的递归也就是把n-2次递归看做一个整体,第n-1次递归是一个简单的操作;……;以此类推,在第2次递归时就是第1次递归的操作+第2次简单的操作,这个过程也是递推,所以当递推到极致时,也就回到了n = 1的最简单的问题上,此时解决掉n = 1时存在的问题,自然,n = 2、3、4、……、n的问题都会被依次解决,这个过程叫做回归。当整个过程结束,其整体被称之为递归。

  1. 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
  2. 函数的局部变量是独立的,不会相互影响
  3. 递归必须向退出递归的条件逼近,否则就是无限递归。
  4. 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用->就会将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁。
//求1 ~ n的和
int sum(int n){
    int result = 0;
    for(int i = 1; i <= n; i++){
        result += i;
    }
    return result;
}
//用简单算法实现代码(使用等差数列公式)
int sum(int n){
    return (1 + n) * n / 2.0;
}
//用递归实现代码
int sum(int n){
    if (n == 1){
        return 1;
    }
    return sum(n - 1) + n;
}
//斐波那契数列  fibonacci
int fibonacci(int n){
    if (n <= 2){
        return 1;
    }
    else{
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

![1](file:///F:/documents/repo/cs-Notes/Language/Cpp/Picture/8%20%E6%B1%89%E8%AF%BA%E5%A1%94.png?lastModify=1789891323)

每次移动必须遵守以下规则:

  1. 每次只能移动柱子最顶端的一个圆盘
  2. 每个柱子上,小圆盘永远要位于大圆盘之上

分析:对于n个圆盘的汉诺塔问题,移动圆盘的过程是:

  1. 将起始柱上的n-1个圆盘移动到辅助柱上;
  2. 将起始柱上遗留的1个圆盘移动到目标柱上;
  3. 将辅助柱上的所有圆盘移动到目标柱上。
//The tower of hanoi
#include <iostream>
using namespace std;
//src->source  tgt->target  sup->support
void hanoi(int num, char src, char tgt, char sup) {
	static int count = 1;	//全局变量
	//如果圆盘的数量只有一个,则直接将其从src移到tgt
	if (num == 1) {
		cout << "第" << count << "次,从" << src << "移动到" << tgt << endl;
		count++;
	}
	else {
		//递归调用hanoi函数,使得n-1个圆盘从src移动到sup上
		hanoi(num - 1, src, sup, tgt);
		//将剩余的最大圆盘从src移动到tgt上
		cout << "第" << count << "次,从" << src << "移动到" << tgt << endl;
		count++;
		//递归调用hanoi函数,使得n-1个圆盘从sup移动到tgt上
		hanoi(num - 1, sup, tgt, src);
	}
}

5.9 内联函数 ▲

在C语言中,如果一些函数被频繁调用,不断的有函数入栈,会造成栈空间或栈内存的大量消耗。在C++中,为了解决这个问题,引入了inline修饰符,表示为内联函数。

栈空间是指放置程序的局部数据,也就是函数内数据的内存空间,在系统中,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程序出错问题,函数的死循环、递归调用的最终结果就是导致栈内存空间枯竭。

5.9.1 使用场景

函数是一个可以重复使用的代码块,CPU会一条一条的挨着执行其中的代码,CPU在执行主调函数时如果遇到了被调函数,主调函数就会暂停,CPU转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。

一个C/C++程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是main(),终点也是main()。当main()调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。

函数调用是有时间和空间开销的,程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能执行函数调用位置以后的代码。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分时间都会花费在函数调用机制上,这种时间开销就不容忽视。

为了消除函数调用的时空开销,C++提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。终止在函数调用处直接嵌入函数体的函数称为内联函数(inline function),又称内嵌函数或者内置函数。

5.9.2 内联函数定义

关键字inline必须与函数定义体放在一起才能起作用,只是放在函数声明处是无效定义。

inline returntype funcname(paramterType params){
	return [val];
}

使用限制:inline的使用是有所限制的,inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)

5.9.3 案例

判断多个数是否为偶数?

#include <iostream>
using namespace std;
inline bool isEven(int num){
	return num % 2 == 0;
}
int main(){
    for(int i = 0; i < 15; i++){
        cout << isEven(i) << endl;
    }
    return 0;
}

5.9.4 内联函数与普通函数的区别

内联函数和普通函数最大的区别在于内部的实现方面,当普通函数在被调用时,系统首先跳跃到该函数的入口地址,执行函数体,执行完成后,再返回到函数调用的地方,函数始终只有一个拷贝。 而内联函数则不需要进行一个寻址的过程,当执行到内联函数时,此函数展开(很类似宏的使用),如果在N个地方调用了此内联函数,则此函数就会有N个代码段的拷贝。

5.10 函数重载 ※

函数重载是C++允许在同一作用域中声明几个类似的同名函数,这些同名函数的形参列表(参数个数、类型、顺序)必须不同(注:其中一项不同即可),常用来处理实现功能类似但数据类型不同的问题。在C++中不仅函数可以重载,运算符也可以重载。

函数重载规则

  1. 函数名称必须相同;
  2. 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等);
  3. 函数的返回类型可以相同也可以不同;
  4. 仅仅返回类型不同不足以成为函数的重载。(见第2条)
 //此样例展示参数类型不同的函数重载
 int sum(int n1, int n2){
     cout << "int";
     return n1 + n2;
 }
 float sum(float f1, float f2){
     cout << "float";
     return f1 + f2;
 }
 double sum(double d1, double d2){
     cout << "double";
     return d1 + d2;
 }

5.11 练习

利用函数求出num1~num2之间的所有素数(num1 < num2)注:算法—埃氏筛数法

 bool isPrime(int num){
     if (num == 1){
         return false;
     }
     bool flag = true;
     for (int i = 2; i <= num / 2 ; i++){
         if (num % i == 0){
             flag = false;
             break;
         }
     }
     return flag;
 }
 void printPrime(int num1, int num2){
     for(int i = num1; i <= num2; i++){
         if(isPrime(i)){
             cout << i << ' ';
         }
     }
 }
 int main(){
     int num1, num2;
     cin >> num1 >> num2;
     printPrime(num1, num2);
 }

求阶乘的和(详见《算法》的高精度部分,这里的n范围为1 < n < 12) input:5 output:153

 #include <iostream>
 using namespace std;
 int n, result;
 int factorial(int key) {
     if (key == 1) {
         return 1;
     }
     else {
         return factorial(key - 1) * key;
     }
 }
 int main() {
     cin >> n;
     for (int i = 1; i <= n; i++) {
         result += factorial(i);
     }
     cout << result << endl;
 }
Philosophy_Mifan
Philosophy_Mifan OP
#6

Chapter 6 变量

6.1 概念

6.1.1 作用域

C++中一个变量或常量在程序中都有一定的作用范围,这个范围称之为作用域。

6.1.2 变量的作用

给一段指定的内存空间起名,方便对这段内存进行操作,也相当于给这片内存空间起个别名

6.2 typedef声明

typedef是在计算机编程语言中为复杂的声明定义一个简单的别名,与宏定义有些许差异。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在一个表达式中。

  1. typedef可以声明各种类型名,但不能用来定义变量。
  2. 用typedef只是对已经存在的类型增加一个类型名,并不能创造一个新的类型。
  3. 当在不同源文件中用到同一类型数据(尤其是数组、指针、结构体、共用体等类型数据)时,常用typedef声明一些数据类型,以提高编程效率。
  4. typedef有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef便于移植
typedef int abc;
abc n = 1;//也就是把abc声明成int,其跟int n = 1完全一致

6.3 局部变量 ※

在函数内部声明/定义的变量叫局部变量,局部变量的作用于仅限于函数内部,同时,在函数内部定义的变量,函数的参数和返回值,if和for结构内部使用的变量都是局部变量。

当存在全局变量和局部变量同名时,在函数内,局部变量会覆盖全局变量。

使用{}可以限制变量的作用域。

6.4 全局变量 ※

在函数外部声明/定义的变量叫全局变量,C++全局变量作用域在整个文件都有效。

6.5 静态变量 ▲

静态存储区的变量是在程序刚开始的时候就会完成初始化,也是整个程序生命周期的唯一一次初始化。静态存储区中一般存储两种变量:static变量和全局变量。

static变量在函数内定义局部变量的时候,作用域只在其函数内,但是生存周期为整个源程序。即只可以在定义该变量的函数内进行使用,但是退出该函数后,该变量一直存在,但不可以进行使用。

int num = 1;
int func(){
	static int num = 5;	//首次进入函数时,num初始化
    return num--;		//后续在进入函数时,num不会被初始化
}
int main(){
    for( ; num <= 5; num++){
        cout << num << "---" << func() << endl;
    }
    return 0;
}

6.6 存储类

存储类用于定义C++程序中变量和函数的生命周期和可见性。 寿命是指变量保持活动的时间段,可见性是指可访问变量的程序的模块。 有五种类型的存储类可在C++中使用:自动(Automatic)、寄存器(Register)、静态(Static)、外部(External)、可变(Mutable)

存储类关键字生命周期可见性初始值
自动auto函数块局部无效值
寄存器register函数块局部无效值
外部extem整个程序局部0
静态static整个程序全局0
可变mutable类局部无效值

6.6.1 auto 自动存储类

auto是所有局部变量的默认存储类。auto关键字自动应用于所有局部变量。 auto int v1; float v2 = 99.1; 上面的例子定义了两个具有相同存储类的变量,auto只能在函数中使用。

6.6.2 static 静态存储类

静态变量只初始化一次,直到程序结束。它保留可在多个函数之间调用的值。静态变量由编译器提供的一个默认值:0

//多次调用此函数查看static int v1与局部变量int v2的区别
void func(){
    static int v1 = 0;	//static varible
    int v2 = 0;			//local varible
    v1++;
    v2++;
    cout << "v1 = " << v1 << "  v2 = " << v2 << endl;
}

6.6.3 register 注册存储类

寄存器变量在寄存器中分配存储器而不是RAM。其大小与寄存器的大小相同。它比其他变量具有更快的访问速度。 建议仅使用寄存器变量进行快速访问。 register int count = 0;

*注:不能得到寄存器变量的地址!

6.6.4 extern 外部存储类

extern变量对所有程序都可见。如果两个或多个文件共享相同的变量或函数就需要使用它。 extern int v1 = 0;

6.6.5 练习

利用全局变量来统计函数被调用的次数

 int cnt = 0;
 void func() {
     cnt++;
 }
 int main() {
     for (int i = 0; i < 100; i++) {
         func();
     }
     cout << "func函数被调用的次数为" << cnt << "次" << endl;
     return 0;
 }
Philosophy_Mifan
Philosophy_Mifan OP
#7

Chapter 7 多文件编程

7.1 什么是多文件编程

为了方便分工或后期的维护,分散代码应遵循一个基本原则:实现相同功能的代码应存储在一个文件中

C++代码文件根据后缀名的不同,大致可以分为如下几类:

  • .h:头文件
  • .hpp:头文件,head plus plus的缩写,混杂着.h的声明和.cpp的定义,OpenCV采用
  • .cpp:源文件,Windows
  • .cc:源文件,Unix/Linux

7.2 多文件编程详细步骤 ※

注:编者以Microsoft Visual Studio 2022演示

Step 1、在解决方案资源管理器中找到头文件

9.png

Step 2、在头文件文件夹处右键->添加->新建项(可使用快捷键Ctrl+Shift+A)

10.png

Step 3、选择头文件(.h)新建即可

11.png

7.3 #include<>和#include""的区别 ▲

#include<>引用的是编译器的类库路径里面的头文件。一般用于引用标准头文件,如stdio.h等

#include ""引用的是项目相对路径中的头文件,一般用于引用自定义的头文件,如果使用#include "",首先会在当前项目所在文件夹下寻找是否有对应的头文件,如果没有,还是会到编译器自带头文件目录中查找。

7.4 防止头文件重复包含 ※

7.4.1 使用宏定义避免重复引入

 #ifndef _NAME_H
 #define _NAME_H
 //头文件内容
 #endif

_NAME_H是宏的名称,必须是独一无二的。

7.4.2 使用#pragma once避免重复引入

#pragma once //放在文件开头的位置

#ifndef是通过定义独一无二的宏来避免重复引入的,也就意味着每次引入头文件都要进行识别,其效率不高,但其移植性很好。

#pragma once不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,效率很高,但其兼容性不是很好。

除此之外,#pragma once只能作用于某个具体的文件,而无法像#ifndef那样仅作用于一段指定的代码。

考虑到编译效率和可移植性,#pragma once和#ifndef经常被结合使用来避免头文件被重复引入

 #pragma once
 #ifndef _NAME_H
 #define _NAME_H
 //头文件内容
 #endif

当编译器可以识别#pragma once时,整个文件仅会被编译一次,当无法识别时,此时才用#ifndef编译。

(还可使用_Pargma关键字避免重复引入)

Philosophy_Mifan
Philosophy_Mifan OP
#8

Chapter 8 指针

8.1 概述

指针,表示的是内存的地址,指针变量也就是保存了内存地址的变量。指针也称为定位符或指示符。内存中字节的编号称为地址或指针。

8.2 定义 ▲

使用指针来保存一个变量的地址,在C++中定义指针变量需要在类型和变量名之间加一个,要知道一个变量的地址只需要在变量前加上&。使用获取一个指针变量所指向的地址的值,也称为指针的间接引用。

 int num = 100;
 int* p = &num;
 cout << "num=" << num << ' ' << "&num=" << &num << endl;
 cout << "*p=" << *p << ' ' << "p=" << p << endl;

output: num=100 &num=000000FF5EEFFA04 *p=100 p=000000FF5EEFFA04

&num和p的值为同一块内存编号,且每运行一次其内存块编号均会发生变化

指针运算

指针变量保存的是变量的地址,变量的地址本质上也是一个整数,因此,可以使用运算符对指针变量进行运算。对指针变量进行自增(++)或自减(--)也叫做指针变量的移动。

8.3 二级指针 ▲

一级指针:将指针指向一个普通的数据类型,这个指针就是一级指针,其保存的是变量的地址。 二级指针:当指针指向一个一级指针的时候,这个指针就是二级指针,依此规律,有多级指针的出现,且指针的级数没有限制。

int num = 1;
int *p = &num;	//一级指针,p指向num的地址
//如果需要保存一个一级指针的地址,就需要用二级指针来保存其地址
int **ptr = &p;	//二级指针,ptr指向p的地址

8.4 指针与数组、函数和字符串的关系

8.4.1 指针和数组 ※

数组是用来保存同一种类型的一组数据的集合,同时,数组中的所有元素在内存中是连续存放的,每个元素的地址在内存中是连续的。在C++中,可以将数组名看做一个指针,它指向数组的第1个元素,也就是索引为0的元素,在C++中,我们将第一个元素的地址称为数组的首地址。

int arr[5] = {1,2,3,4,5};
for (int i = 0; i < 5; i++){
    cout << "a[" << i << "]" << *(arr + i) << " ";
    //cout << "a[" << i << "]" << *(arr++) << " ";  报错,arr是数组名同时也是常量,不能对其进行更改
}

使用指针指向数组名 type *指针名 = 数组名; int *p = arr; //定义了一个指针p,指向arr数组,可以直接通过指针的移动来访问数组中的元素。

int arr[5] = {1,2,3,4,5};
int *p = arr;
for (int i = 0; i < 5; i++){
    cout << "a[" << i << "]" << *(p + i) << " ";
    cout << "a[" << i << "]" << *(p++) << " ";  //此时,p是一个指针,是变量,可以对其进行更改
}

8.4.2 指针和函数 ※

函数的参数不仅可以是基本的数据类型,也可以是指针,用指针变量作为函数的参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而销毁。当需要在函数内部修改传入的基本类型的参数的值进行修改的话,只能通过指针的方式来传递参数。

void func1(int* v1) {
	(*v1)++;	//不使用指针(*v1),v1虽然会在外部被改为101,但当函数结束后,内部值并没有被修改,输出依旧为100
}
int main() {
	int num = 100;
	cout << num << endl;
	func1(&num);	//指针作为函数参数的时候,需要传入地址,而非变量值
	cout << num << endl;
	return 0;
}

当然,函数的返回值同样可以是指针类型,用来返回一个变量的地址,但不可以返回局部变量的变量值,这是因为在函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形参。

int* func(int* num) {
	*(num) += 100;
	return num;
}
int main() {
	int v1 = 1;
	cout << v1 << endl;
	int* p = func(&v1);
	cout << v1 << endl;
	cout << *p << endl;
	return 0;
}

8.4.3 指针和字符串 ※

字符串是可以通过字符数组来实现的,因此,关于数组指针的相关操作同样适用于字符串,同时还可以直接通过指针的方式来定义一个字符串。

const char* pc = "abc";
cout << pc << endl;		//输出为abc
cout << *pc << endl;	//输出为a

通过指针对字符串进行遍历

int main() {
	char str[] = "hello world";
	int len = strlen(str);
	char* ptr = str;
	for (int i = 0; i < len; i++) {
		cout << *ptr++;
	}
	return 0;
}

8.5 NULL指针和void指针 ▲

8.5.1 定义

当一个指针不指向任何数据时,称之为空指针,用NULL来表示。注意,NULL是区分大小写的,即NULL不能写成null。在定义指针时或指针使用完毕后不再指向任何数据时,推荐将指针设置为NULL,以表明该指针不再指向任何数据 #define NULL((void *)0) (void *)0表示把数值0强制转换为类型(void *)

在定义指针变量时int* p;这种指针定义时不指向任何地址,也就是野指针,建议定义为int *p = NULL; 在C++98中,字面常量0的类型既可以是整型,也可以是一个无类型指针(void*),在某些场景(如:函数重载)的使用中会遇到麻烦,C++11中引入了关键字nullptr来避免二义性,nullptr是一个“指针空值常量”,仅可以被隐式转换为指针类型,故也推荐以下定义方法 int* p = nullptr;

void是指无类型,void指针也就是无类型指针,void指针可以指向任何类型的数据,所以void指针一般被称为通用指针或者泛指针,也可以叫做万能指针。

8.5.2 void指针的使用

  1. 在C++中,任何时候都可以使用void类型的指针来答题其他类型的指针,void指针可以指向任何数据类型的变量。
  2. 如果要通过void指针去获取它所指向的变量值时,需要先将void指针强制类型转换成和变量名类型相匹配的数据类型指针后再进行操作。
  3. 任何类型的指针都可以赋值给void指针,无需强制类型转换。
int num = 1;
void* p = &num;
cout << "p = " << p << endl;
cout << "*p = " << ((int*)p) << endl;//void指针不能直接使用,必须经过强制类型转换后才能使用

8.6 指针与引用的区别 ※

指针的本质是一个变量的地址,引用是一个变量的别名。且定义指针值可以不初始化,并且可以定义空指针,但定义引用时必须初始化,且不能为空引用。每一种编程语言都使用指针,只是C++将指针暴露给了用户,Java、C#等则将指针隐藏起来了。

  1. 从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。
  2. 从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
  3. 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应的地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改,从某种意义上来说,引用可以被认为是不能改变的指针。
  4. 理论上,对于指针的级数没有限制,但是引用只能是一级。

8.7 指针常量与常量指针 ※

1.const修饰常量(指针常量)

int num1 = 1, num2 = 2;
int* const p = &num1;
*p = 5;		//由于p被const修饰,所以p是不能进行更改的,就是指针的值是不能更改的,也就是指针的指向也是不能更改的。
//p = &num2;	//不被允许的行为

2.const修饰指针(常量指针)

int num1 = 1, num2 = 2;
const int *p = &num1;
p = &num2;
//*p = 5;	//由于const修饰的是*p,所以*p不能被修改

8.8 引用和常量引用

引用的本质是指针常量,即int* const p = &num;特点就是,其指向的值能改变,指针的指向不能改变。

8.9 练习

实现找出两个数中的最大值

 void checkMax(int* p1, int* p2, int* pmax) {
     if (*p1 > *p2) {
         *pmax = *p1;
     }
     else {
         *pmax = *p2;
     }
 }
 int main() {
     int v1 = 5;
     int v2 = 10;
     int max;
     checkMax(&v1, &v2, &max);
     cout << max << endl;
     return 0;
 }
Sign in to reply.Sign in