1. C++基础语法

文章目录

展开

1.0 第一个C++程序

1.1 注释

1.1.1 单行注释(行内注释)

1.1.2 多行注释(注释块)

1.1.3 注释用途

  • 解释程序的意思
  • 让某段代码不执行(调试)
  • 便于日后维护、他人阅读

1.2 输入与输出

1.2.1 cin 与 cout

变量 见后文。

1.2.2 scanf 与 printf

  • 是 C 语言提供的,C++ 向下兼容的。
  • 大多数情况下速度比 cin 和 cout 快。
  • 可以方便地控制输入输出格式。

其中, %d 表示读入/输出的变量是一个有符号整型(int 型)的变量。

类似地:

  • %s 表示字符串(string)。
  • %c 表示字符(char)。

  • %lf 表示双精度浮点数(double)。

  • %lld 表示长整型(long long)。
  • %u 表示无符号整型(unsigned int)。
  • %llu 表示无符号长整型(unsigned long long)。

除了类型标识符以外,还有一些控制格式的方式。

  • %1d 表示长度为1的整型。在读入时,即使没有空格也可以逐位读入数字。在输出时,若指定的长度大于数字的位数,就会在数字前用空格填充。若指定的长度小于数字的位数,就没有效果。

  • %.6lf ,用于输出,保留六位小数。

1.2.3 转义字符

转义字符用来表示一些无法直接输入的字符,如由于字符串字面量中无法换行而无法直接输入的换行符,由于有特殊含义而无法输入的引号,由于表示转义字符而无法输入的反斜杠等。

常见的转义字符有:

  • \t 表示制表符,即一个tab,通常为4个空格大小。
  • \\ 表示 \
  • \" 表示 "
  • \0 表示空字符,用来表示C风格字符串的结尾。
  • \r 表示回车。Linux系统换行符为 \n,Windows系统换行符为 \r\n 。在 OI 中,如果输出需要换行,使用 \n 即可。但读入时,如果使用逐字符读入,可能会由于换行符造成一些问题,需要注意。例如,gets\n 作为字符串结尾,这时候如果换行符是 \r\n\r 就会留在字符串结尾。
  • 特殊地,%% 表示 %,只能用在 printfscanf 中,在其他字符串字面量中只需要简单使用 % 就好了。

1.3 变量

1.3.1 数据类型

C++ 的类型系统由如下几部分组成:

  1. 基础类型

    a. 无类型/void

    b. 空指针类型

    c. 算数类型

    ​ i. 整数类型(int

    ​ ii. 布尔类型(bool

    ​ iii. 字符类型(char

    ​ iv. 浮点类型(floatdouble

  2. 复合类型

1.3.1.1 布尔类型(bool)

值只有两种:truefalse

一般情况下占 1 字节的空间。

1.3.1.2 整数类型

5 种:charshortintlonglong long

区别:

  • 存储所占空间不同
  • 表示的范围不同

short < int < long long

符号性:

  • signed:表示带符号整数(默认);
  • unsigned:表示无符号整数。
1.3.1.3 字符类型

char,底层存储方式仍然是整数,一般通过 ASCII 编码实现字符和整数的一一对应。

‘a’:97 ‘A’:65

1.3.1.4 浮点类型(小数)

floatdouble。区别是位宽不同,进而能表示的范围和精度不同。

1.3.1.5 无类型

void,只能用于声明函数,表示函数无返回值。

1.3.2 类型转换

小类型可以转换到大类型。

例如:

大类型转换到小类型可能丢失精度。

例如:

1.3.3 定义变量

区分全局变量(花括号外的)和局部变量(花括号内的),前者会被初始化成0,后者没有这种特性。

变量标识符:数字、字母和下划线,不能以数字开头,不能是关键字。

  • 合法:abc, abc123, _abc123
  • 不合法:123abc, *abc, int
1.3.3.1 变量作用域

全局变量的作用域,自其定义之处开始,至文件结束为止。

局部变量的作用域,自其定义之处开始,至代码块结束为止。

由花括号括起的若干语句就是一个代码块。

如果一个代码块的内嵌块中定义了相同变量名的变量,则内层块中将无法访问外层块中相同变量名的变量。

为了防止出现意料之外的错误,请尽量避免局部变量与全局变量重名的情况。

1.3.4 常量

常量是固定值,在程序执行期间不会改变。

常量的值在定义后不能被修改。定义时加一个 const 关键字即可。

如果修改了常量的值,在编译环节就会报错:error: assignment of read-only variable‘a’

1.4 运算

1.4.1 算术运算符

运算符 功能
+ (单目)
- (单目)
* (双目) 乘法
/ 除法
% 取模
+ (双目) 加法
- (双目) 减法

算术运算符中有两个单目运算符(正、负)以及五个双目运算符(乘法、除法、取模、加法、减法),其中单目运算符的优先级最高。

其中取模运算符 % 意为计算两个整数相除得到的余数,即求余数。

- 为双目运算符时做减法运算符,如 2-1 ;为单目运算符时做负值运算符,如 -1

遵循数学中加减乘除的优先规律,首先进行优先级高的运算,同优先级自左向右运算,括号提高优先级。

1.4.1.1 算术运算中的类型转换

对于双目算术运算符,当参与运算的两个变量类型相同时,不发生类型转换 ,运算结果将会用参与运算的变量的类型容纳,否则会发生类型转换,以使两个变量的类型一致。

转换的规则如下:

  • 先将 charboolshort 等类型提升至 int (或 unsigned int ,取决于原类型的符号性)类型;
  • 若存在一个变量类型为 long double ,会将另一变量转换为 long double 类型;
  • 否则,若存在一个变量类型为 double ,会将另一变量转换为 double 类型;
  • 否则,若存在一个变量类型为 float ,会将另一变量转换为 float 类型;

例如,对于一个整型( int )变量 x 和另一个双精度浮点型( double )类型变量 y

  • x/3 的结果将会是整型;
  • x/3.0 的结果将会是双精度浮点型;
  • x/y 的结果将会是双精度浮点型;
  • x*1/3 的结果将会是整型;
  • x*1.0/3 的结果将会是双精度浮点型;

1.4.2 位运算符

1.4.2.1 进位制

二进制(0b)、八进制(0)、十六进制(0x)、十进制。

进制间的相互转化:以十进制为桥梁。

0~9,逢十进一

X进制(表示数字的方法,记数符号):0~x-1,逢x进一

二进制:10,111,101,1111,11010101

八进制:6743547

十六进制:A(10)B(11)C(12)D(13)E(14)F(15),63AF

二进制(X进制)转十进制,按位权法

(111010)_ 2=(58)_ {10}

(136)_ 8=(94)_ {10}

(A6F)_ {16}=(2671)_ {10}

十进制转二进制(X进制),除法取余法

(58)_{10}=(111010)_2

二进制转十六进制

01 1101 0010 1010

​ 1 D 2 A

1.4.2.2 位运算(二进制上每一位的运算)
运算 运算符 解释
& 只有两个对应位都为 1 时才为 1
| 只要两个对应位中有一个 1 时就为 1
异或 ^ 只有两个对应位不同时才为 1
取反 ~ 1 变 0,0 变 1
左移 << 将 num 的二进制表示向左移动 i 位所得的值
右移 >> 将 num 的二进制表示向右移动 i 位所得的值

与运算(交集):101&110=100,有0即0,没0即1。

或运算(并集):101|100=101,有1即1,没1即0。

异或运算:相同为0,不同为1。1010^1101=0111。

取反:按位取反。~1010=0101。

左移:(舍位)1011001<<3=1011。

右移:(补零)1011001>>3=1011001000。

1.4.3 自增/自减运算符

有时我们需要让变量进行增加 1(自增)或者减少 1(自减),这时自增运算符 ++ 和自减运算符 -- 就派上用场了。

自增/自减运算符可放在变量前或变量后面,在变量前称为前缀,在变量后称为后缀,单独使用时前缀后缀无需特别区别,如果需要用到表达式的值则需注意,具体可看下面的例子。

1.4.4 复合赋值运算符

复合赋值运算符实际上是表达式的缩写形式。

可分为复合算术运算符 +=-=*=/=%= 和复合位运算符 &=|=^=<<=>>=

例如,op = op + 2 可写为 op += 2op = op - 2 可写为 op -= 2op= op * 2 可写为 op *= 2

1.4.5 比较运算符(得到一个bool值)

运算符 功能
> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于
!= 不等于

1.4.6 条件运算符(三目运算符)

a ? b : c 中如果表达式 a 成立,那么这个条件表达式的结果是 b,否则条件表达式的结果是 c

1.4.7 逻辑运算符

运算符 功能
&& 逻辑与
|| 逻辑或
! 逻辑非

1.4.8 运算符优先级

原则:不清楚优先级就套括号。

1.5 流程控制语句

1.5.1 分支

1.5.1.1 if 语句

基本 if 语句

if…else 语句

if…else if…else 语句

1.5.1.2 switch语句

选择句可以是算术值,可以是数值(变量),可以是字符。

switch 语句执行时,先求出选择句的值,然后根据选择句的值选择相应的标签,从标签处开始执行。

其中,选择句必须是一个整数类型表达式,而标签都必须是整数类型的常量。例如:

switch 语句中还要根据需求加入 break 语句进行中断,否则在对应的 case 被选择之后接下来的所有 case 里的语句和 default 里的语句都会被运行。

以上代码运行后输出的结果为 WIKIHello World,如果不想让下面分支的语句被运行就需要 break 了,具体例子可看下面的示例。

以上代码运行后输出的结果为 WIKI,因为 break 的存在,接下来的语句就不会继续被执行了。最后一个语句不需要 break,因为下面没有语句了。

处理入口编号不能重复,但可以颠倒。也就是说,入口编号的顺序不重要。各个 case(包括 default)的出现次序可任意。例如:

1.5.2 循环

1.5.2.1 for 语句

for 语句的三个部分中,任何一个部分都可以省略。其中,若省略了判断条件,相当于判断条件永远为真。

1.5.2.2 while 语句

for和while用法的区别

for适用于已知循环次数的情况

while相反

1.5.2.3 do…while 语句

与 while 语句的区别在于,do…while 语句是先执行循环体再进行判断的。

三种语句可以彼此代替,但一般来说,语句的选用遵守以下原则:

  1. 循环过程中有个固定的增加步骤(最常见的是枚举/遍历)时,使用 for 语句;
  2. 只确定循环的终止条件时,使用 while 语句;
  3. 使用 while 语句时,若要先执行循环体再进行判断,使用 do…while 语句。一般很少用到,常用场景是用户输入。

例题P5718

例题T357607

1.5.2.4 break 和 continue 语句

break 语句的作用是退出循环。

continue 语句的作用是跳过循环体的余下部分。

1.5.2.5 循环嵌套

1.6 高级数据类型

1.6.1 数组

数组是存放相同类型对象的容器,数组中存放的对象没有名字,而是要通过其所在的位置访问。

数组的大小是固定的,不能随意改变数组的长度。

1.6.1.1 定义数组

数组的声明形如 a[d],其中,a 是数组的名字,d 是数组中元素的个数。

在编译时,d 应该是已知的,也就是说,d 应该是一个整型的常量表达式。

不能将一个数组直接赋值给另一个数组:

1.6.1.2 访问数组元素

可以通过下标运算符 [] 来访问数组内元素,数组的索引(即方括号中的值)从 0 开始。

以一个包含 10 个元素的数组为例,它的索引为 0 到 9,而非 1 到 10。

但在 OI 中,为了使用方便,我们通常会将数组开大一点,不使用数组的第一个元素,从下标 1 开始访问数组元素。

例 1:从标准输入中读取一个整数n,再读取n个数,存入数组中。其中,n\leq1000

例 2:(接例 1)求和数组 arr 中的元素,并输出和。满足数组中所有元素的和小于等于 2^{31}-1

数组的下标idx 应当满足 0\leq{idx}<size ,如果下标越界,则会产生不可预料的后果,如段错误(Segmentation Fault),或者修改预期以外的变量。

例题P1427 小鱼的数字游戏

1.6.1.3 多维数组

多维数组的实质是「数组的数组」,即外层数组的元素是数组。

一个二维数组需要两个维度来定义:数组的长度和数组内元素的长度。访问二维数组时需要写出两个索引:

我们经常使用嵌套的 for 循环来处理二维数组。

例:从标准输入中读取两个数 nm,分别表示黑白图片的高与宽,满足 n,m\leq1000

对于接下来的 n 行数据,每行有用空格分隔开的 m 个数,代表这一位置的亮度值。现在我们读取这张图片,并将其存入二维数组中。

同样地,你可以定义三维、四维,以及更高维的数组。

1.6.2 结构体

结构体(struct),可以看做是一系列称为成员元素的组合体。

可以看做是自定义的数据类型。

1.6.2.1 定义结构体

上例中定义了一个名为 Object 的结构体,两个成员元素 value, weight,类型都为 int

} 后,定义了数据类型为 Object 的常量 a,变量 b,变量 tmp,数组 B

对于某种已经存在的类型,都可以使用这里的方法进行定义常量、变量、数组等。

1.6.2.2 访问/修改成员元素

可以使用 变量名.成员元素名 进行 访问。

如 : 输出 varv 成员:cout << var.v

如 : 将结构体指针 ptr 指向的结构体的成员元素 v 赋值为 tmp(*ptr).v = tmp 或者 ptr->v = tmp

1.6.2.3 为什么需要结构体

首先,条条大路通罗马,可以不使用结构体达到相同的效果。但是结构体能够显式地将成员元素(在算法竞赛中通常是变量)捆绑在一起,如本例中的 Object 结构体,便将 value,weight 放在了一起(定义这个结构体的实际意义是表示一件物品的重量与价值)。这样的好处边是限制了成员元素的使用。
想象一下,如果不使用结构体而且有两个数组 value[], weight[],很容易写混淆。但如果使用结构体,能够减轻出现使用变量错误的几率。

并且不同的结构体(结构体类型,如 Object 这个结构体)或者不同的结构体变量(结构体的实例,如上方的 e 数组)可以拥有相同名字的成员元素(如 tmp.value,b.value),同名的成员元素相互独立(拥有独自的内存,比如说修改 tmp.value 不会影响 b.value 的值)。
这样的好处是可以使用尽可能相同或者相近的变量去描述一个物品。比如说 Object 里有 value 这个成员变量;我们还可以定义一个 Car 结构体,同时也拥有 value 这个成员;如果不使用结构体,或许我们就需要定义 valueOfObject[],valueOfCar[] 等不同名称的数组来区分。

例题P5744【深基7.习9】培训

1.6.3 字符串

字符串是一种特殊的序列,它是由字符构成的一串序列。

1.6.3.1 概念

设有字符串 S="abcdefg"

子串:表示从母串(源串)中截取的一段连续的字符串,如S1="cde"

子序列:表示从母串(源串)中截取的一段不一定连续的、但相对顺序不变的字符串,如S2="aceg"

后缀:表示母串(源串)的末尾若干个字符,如S3="efg"。特别的,除了母串本身以外的其他后缀叫真后缀。

前缀:表示母串(源串)的前面若干个字符,如S4="abc"。特别的,除了母串本身以外的其他前缀叫真前缀。

字典序:以第 i 个字符作为第 i 关键字进行大小比较。如"abc">"bb""efg">"egfd"等等。

回文串:正着写和倒着写相同的字符串,如S5="abcba"

1.6.3.2 字符串的存储
  1. 我们学过数组,就可以用数组的方式来存储字符串,即char s[200]="abcdef";表示声明一个长度空间为200的字符串。
  2. C++中可以用string关键字声明字符串,如string s="abcdef";
1.6.3.2 C++标准库

C++、Java、Python等语言火爆的原因除了性能好,更突出的一点是它们有良好的社区,这导致我们可以使用前人写好的代码。

当我们使用string s="abcdef";声明字符串时,有如下函数可以使用:

  1. s.size()s.length()返回字符串中'\0'之前的字符个数,如果字符串为"abc\0abc",则返回3。
  2. s.find(ch, start = 0) 查找并返回从 start 开始的字符 ch 的位置;s.rfind(ch) 从末尾开始,查找并返回第一个找到的字符 ch 的位置(皆从 0 开始)(如果查找不到,返回 -1)。
  3. s.substr(start, len) 可以从字符串的 start(从 0 开始)截取一个长度为 len 的字符串(缺省 len 时代码截取到字符串末尾)。
  4. s.append(s)s 添加到字符串末尾。
  5. s.append(s, pos, n) 将字符串 s 中,从 pos 开始的 n 个字符连接到当前字符串结尾。
  6. s.replace(pos, n, s) 删除从 pos 开始的 n 个字符,然后在 pos 处插入串 s
  7. s.erase(pos, n) 删除从 pos 开始的 n 个字符。
  8. s.insert(pos, s)pos 位置插入字符串 s

1.7 函数

1.7.1 函数的声明

编程中的函数(function)一般是若干语句的集合。我们也可以将其称作「子过程(subroutine)」。在编程中,如果有一些重复的过程,我们可以将其提取出来,形成一个函数。函数可以接收若干值,这叫做函数的参数。函数也可以返回某个值,这叫做函数的返回值。

声明一个函数,我们需要返回值类型、函数的名称,以及参数列表。

如上,我们声明了一个名为 some_function 的函数,它需要接收两个 int 类型的参数,返回值类型也为 int。可以认为,这个函数将会对传入的两个整数进行一些操作,并且返回一个同样类型的结果。

1.7.2 实现函数:编写函数的定义

只有函数的声明(declaration)还不够,他只能让我们在调用时能够得知函数的 接口 类型(即接收什么数据、返回什么数据),但其缺乏具体的内部实现,也就是函数的 定义(definition)。我们可以在 声明之后的其他地方 编写代码 实现(implement)这个函数(也可以在另外的文件中实现,但是需要将分别编译后的文件在链接时一并给出)。

如果函数有返回值,则需要通过 return 语句,将值返回给调用方。函数一旦执行到 return 语句,则直接结束当前函数,不再执行后续的语句。

在定义时,我们给函数的参数列表的变量起了名字。这样,我们便可以在函数定义中使用这些变量了。

如果是同一个文件中,我们也可以直接将 声明和定义合并在一起,换句话说,也就是在声明时就完成定义。

如果函数不需要有返回值,则将函数的返回值类型标为 void;如果函数不需要参数,则可以将参数列表置空。同样,无返回值的函数执行到 return; 语句也会结束执行。

1.7.3 函数的调用

和变量一样,函数需要先被声明,才能使用。使用函数的行为,叫做「调用(call)」。我们可以在任何函数内部调用其他函数,包括这个函数自身。函数调用自身的行为,称为 递归(recursion)。

在大多数语言中,调用函数的写法,是 函数名称加上一对括号 (),如 foo()。如果函数需要参数,则我们将其需要的参数按顺序填写在括号中,以逗号间隔,如 foo(1, 2)。函数的调用也是一个表达式,函数的返回值 就是 表达式的值

函数声明时候写出的参数,可以理解为在函数 当前次调用的内部 可以使用的变量,这些变量的值由调用处传入的值初始化。看下面这个例子:

在上面的例子中,foo(a, b) 是一次对 foo 的调用。调用时,foo 中的 xy 变量,分别由调用处 ab 的值初始化。因此,在 foo 中对变量 xy 的修改,并不会影响到调用处的变量的值

如果我们需要在函数(子过程)中修改变量的值,则需要采用「传引用」的方式。

上述代码中,我们看到函数参数列表中的「int」后面添加了一个「&(and 符号)」,这表示对于 int 类型的 引用(reference)。在调用 foo 时,调用处 ab 变量分别初始化了 foo 中两个对 int 类型的引用 xy。在 foo 中的 xy,可以理解为调用处 ab 变量的「别名」,即 foo 中对 xy 的操作,就是对调用处 ab 的操作。

例题B2128

【黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难】

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注