c-c基础入门(完结)
前言
- 本篇旨在帮助初学者快速上手
C/C++语言基本语法和基础操作。- 由于篇幅有限及为激发读者动手能力,本篇部分内容除案例外暂不详细展开。例如:数组及函数,除案例所示
int类型外,还有double、char等其他类型,需读者自行上机尝试。 - 本篇关于
C++语言中面向对象编程的内容单独成篇,具体参阅C++面向对象程序设计。 - 本篇大量参考cppreference.com、OI Wiki。
- 鉴于本人水平有限,文中不足与错误之处以
C/C++官方文档为准,如有建议,欢迎指出。 - 推荐初学者参考本篇学习之余进行练习:
- 由于篇幅有限及为激发读者动手能力,本篇部分内容除案例外暂不详细展开。例如:数组及函数,除案例所示
- 练习平台:计算机学院线上评测OJ
- 参考题解:HAUE计算机学院OJ题解
1. C/C++初识
1.1 第一个C/C++程序
C程序:
#include <stdio.h>
int main() {
printf("Hello, world!");
return 0;
}C++程序:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!";
return 0;
}关于 #include
#include是一个预处理命令,用于将指定文件的内容插入到该指令处,被插入的文件称为头文件。- 头文件中包含一些初始定义,例如在
C语言的头文件<stdio.h>中包含了对printf()函数的声明,因此添加#include <stdio.h>后便可直接使用printf()函数。 - 除了
C语言常用的#include <stdio.h>和C++常用的#include <iostream>外,还有许多其他头文件,它们包含不同的函数原型或模板,使用相应函数时需包含对应的头文件。 #include也可用于包含用户自定义的头文件,此时应使用双引号("")而非尖括号(<>)。
- 头文件中包含一些初始定义,例如在
关于 using namespace std;
std 是 C++ 标准库所使用的命名空间。
C++的命名空间机制可以用来解决复杂项目中名字冲突的问题。- 例如:
C++标准库的所有内容均定义在std命名空间中,如果你定义了一个叫cout的变量,则可以通过cout来访问你定义的cout变量,通过std::cout访问标准库的cout对象,而不用担心产生冲突。 using namespace std;会将std中的所有名字引入,我们就可以用cout代替std::cout。
#include <iostream>
int main(){
std::cout << "Hello,world!" ;
return 0;
}关于 main()
main()称为主函数,程序运行时就会先执行main()中的代码。- 实际上,
main函数是由系统或外部程序调用的。如,你在命令行中调用了你的程序,也就是调用了你程序中的main函数。
- 实际上,
关于 return 0;
return 0;表示程序运行main函数后的返回值,在一般情况下,程序结束时返回 \(0\) 表示一切正常,否则返回值表示错误代码。- 在 C 或 C++ 中,非零返回值通常用于表示错误。
- 如果不写
return语句的话,程序正常结束默认返回值也是 \(0\)。
1.2 注释
作用:
- 可以用来解释程序的意思,还可以让某段代码不执行(但是依然保留在源文件里)。
- 在代码中加一些说明和解释,方便自己或其他程序员阅读代码。
两种格式:
- 单行注释:
// 描述信息- 通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明。
- 多行注释:
/* 描述信息 */- 通常放在一段代码的上方,对该段代码做整体说明。
> 提示:编译器在编译代码时,会忽略注释的内容,注释对程序运行没有影响,C/C++通用该规则。
示例:
#include <stdio.h> //这是C语言的头文件
int main(){ //main()函数
printf("Hello,world!"); //输出Hello,world!
/* 这是多行注释,从本行开始
printf("Hello,world!"); //输出Hello,world!
截止至该行,以上的所有内容不会执行 */
return 0;
}1.3 数据的输入输出
输入:
C语法:scanf()C++语法:cin >>
输出:
C语法:printf()C++语法:cout <<
C语言代码示例:
#include <stdio.h>
int main() {
int a; // 定义一个变量a
scanf("%d", &a); // 输入a的值
printf("%d", a); // 输出a的值
return 0;
}C++语言代码示例:
#include <iostream>
using namespace std;
int main() {
int a; // 定义一个变量a
cin >> a; // 输入a的值
cout << a; // 输出a的值
return 0;
}关于变量:
- 可以简单理解为一个名字及其对应的值。
- 变量有其相关的数据类型,如整型
int、双精度浮点型double等。
关于 scanf("%d", &a);:
scanf()是 C 语言的输入函数,""内为格式化字符串。%d表示读入一个有符号整型(int型)的值。&a表示将读入的数据存入变量a的内存地址。
关于 printf("%d", a);:
printf()是 C 语言的输出函数,""内为格式化字符串。%d表示输出一个有符号整型(int型)的值。a表示输出变量a的值。
关于 cin >> a;:
cin >>是 C++ 语言的输入流操作符。cin在输入时会自动匹配变量的数据类型。
关于 cout << a;:
cout <<是 C++ 语言的输出流操作符。cout在输出时会自动识别变量的数据类型。
2. 数据类型
2.1 变量
作用:
- 给一段指定的内存空间起名,方便操作这段内存。
- 变量用于存储对应数据类型的数据。
语法:
数据类型 变量名;数据类型 变量名 = 初始值;
C语言代码示例:
#include <stdio.h>
int main() {
int a; // 定义一个int型的变量a
int b = 10; // 定义一个int型的变量b,初始值为10
scanf("%d", &a);
printf("%d %d", a, b); // 输出a的值后输出一个空格,再输出b的值
return 0;
}C++语言代码示例:
#include <iostream>
using namespace std;
int main() {
int a; // 定义一个int型的变量a
int b = 10; // 定义一个int型的变量b,初始值为10
cin >> a;
cout << a << " " << b; // 输出a的值后输出一个空格,再输出b的值
return 0;
}2.2 整型
作用:
- 用于存储整数。
- 在
C++标准中,规定int的位数至少为 16 位,事实上在现在的绝大多数平台,int的位数均为 32 位。
- 在
对照表:
| 数据类型 | 占用空间 | 取值范围 |
|---|---|---|
short | 2 字节 | \((-2^{15} \sim 2^{15}-1)\) |
int | 4 字节 | \((-2^{31} \sim 2^{31}-1)\) |
long | Windows 为 4 字节,Linux 为 4 字节 (32 位),8 字节 (64 位) | \((-2^{31} \sim 2^{31}-1)\) |
long long | 8 字节 | \((-2^{63} \sim 2^{63}-1)\) |
unsigned long long | 8 字节 | \((0\sim 2^{64}-1)\) |
常用的整型输入和输出:
int:对应scanf()读入和printf()输出应为%d。long long:对应scanf()读入和printf()输出应为%lld。
C 语言代码示例:
#include <stdio.h>
int main(){
int a; // 定义一个int型的变量a
long long b; // 定义一个long long型的变量b
scanf("%d %lld", &a, &b); // 读入a和b的值
printf("%d %lld", a, b); // 输出a的值后输出一个空格,再输出b的值
return 0;
}C++ 语言代码示例:
#include <iostream>
using namespace std;
int main(){
int a; // 定义一个int型的变量a
long long b; // 定义一个long long型的变量b
cin >> a >> b; // 读入a和b的值
cout << a << " " << b; // 输出a的值后输出一个空格,再输出b的值
return 0;
}2.3 浮点型
作用:
- 存储浮点数。
float保留 6 位小数,double保留 15 位小数。
对照表:
| 数据类型 | 占用空间 | 有效数字范围 |
|---|---|---|
float | 4 字节 | \((-3.4\times 10^{-38}\sim 3.4\times 10^{38})\) |
double | 8 字节 | \((-1.7\times 10^{-308}\sim 1.7\times 10^{308})\) |
常用的浮点型输入和输出:
float:对应scanf()读入和printf()输出应为%f。double:对应scanf()读入和printf()输出应为%lf。printf()在输出浮点数时,保留x位小数可以使用%.xf,%.xlf。cout <<在输出浮点数时,保留x位小数,需要加上头文件#include <iomanip>,然后cout << setprecision(x)。
C语言代码示例:
#include <stdio.h>
int main(){
float a; //定义一个float型的变量a
double b; //定义一个double型的变量b
scanf("%f %lf", &a, &b); //读入a和b的值
printf("%f %lf\n", a, b); //输出a的值后输出一个空格,再输出b的值再输出回车
printf("%.2f %.3lf\n", a, b); //输出a的值保留2位小数后输出一个空格,再输出b的值保留3位小数再输出回车
return 0;
}C++语言代码示例:
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
float a; //定义一个float型的变量a
double b; //定义一个double型的变量b
cin >> a >> b; //读入a和b的值
cout << a << " " << b << endl; //输出a的值后输出一个空格,再输出b的值再输出回车
cout << setprecision(2) << a << " " << setprecision(3) << b << endl; //输出a的值保留2位小数后输出一个空格,再输出b的值保留3位小数再输出回车
return 0;
}了解更多:对于数字是如何在计算机中存储的,可以参考数字在计算机中的表示
2.4 字符型
作用:
- 存储单个字符。
- 存储字符为其对应的ASCII编码。
语法:
char 变量名;char 变量名 = 'a';
注意:
- 在初始化字符型变量时,用单引号将字符括起来,不要用双引号。
- 单引号内只能有一个字符如
b,c等,可以是空格符,但不可以是字符串如"abcd"。 C和C++中字符型变量只占用 \(1\) 个字节。
- 单引号内只能有一个字符如
字符型输入和输出:
char:对应scanf()读入和printf()输出应为%c。
C语言代码示例:
#include <stdio.h>
int main() {
char a; // 定义一个char型的变量a
scanf("%c", &a); // 读入a值
printf("%c\n", a); // 输出a的值后输出一个回车
return 0;
}C++语言代码示例:
#include <iostream>
using namespace std;
int main() {
char a; // 定义一个char型的变量a
cin >> a; // 读入a值
cout << a << endl; // 输出a的值后输出一个回车
return 0;
}ASCII码表格:
| ASCII 值 | 控制字符 | ASCII 值 | 字符 | ASCII 值 | 字符 | ASCII 值 | 字符 |
|---|---|---|---|---|---|---|---|
| 0 | NUL | 32 | (空格) | 64 | @ | 96 | ` |
| 1 | SOH | 33 | ! | 65 | A | 97 | a |
| 2 | STX | 34 | " | 66 | B | 98 | b |
| 3 | ETX | 35 | # | 67 | C | 99 | c |
| 4 | EOT | 36 | $ | 68 | D | 100 | d |
| 5 | ENQ | 37 | % | 69 | E | 101 | e |
| 6 | ACK | 38 | & | 70 | F | 102 | f |
| 7 | BEL | 39 | ' | 71 | G | 103 | g |
| 8 | BS | 40 | ( | 72 | H | 104 | h |
| 9 | HT | 41 | ) | 73 | I | 105 | i |
| 10 | LF | 42 | * | 74 | J | 106 | j |
| 11 | VT | 43 | + | 75 | K | 107 | k |
| 12 | FF | 44 | , | 76 | L | 108 | l |
| 13 | CR | 45 | - | 77 | M | 109 | m |
| 14 | SO | 46 | . | 78 | N | 110 | n |
| 15 | SI | 47 | / | 79 | O | 111 | o |
| 16 | DLE | 48 | 0 | 80 | P | 112 | p |
| 17 | DC1 | 49 | 1 | 81 | Q | 113 | q |
| 18 | DC2 | 50 | 2 | 82 | R | 114 | r |
| 19 | DC3 | 51 | 3 | 83 | S | 115 | s |
| 20 | DC4 | 52 | 4 | 84 | T | 116 | t |
| 21 | NAK | 53 | 5 | 85 | U | 117 | u |
| 22 | SYN | 54 | 6 | 86 | V | 118 | v |
| 23 | ETB | 55 | 7 | 87 | W | 119 | w |
| 24 | CAN | 56 | 8 | 88 | X | 120 | x |
| 25 | EM | 57 | 9 | 89 | Y | 121 | y |
| 26 | SUB | 58 | : | 90 | Z | 122 | z |
| 27 | ESC | 59 | ; | 91 | [ | 123 | { |
| 28 | FS | 60 | < | 92 | \ | 124 | |
| 29 | GS | 61 | = | 93 | ] | 125 | } |
| 30 | RS | 62 | > | 94 | ^ | 126 | ~ |
| 31 | US | 63 | ? | 95 | _ | 127 | DEL |
ASCII 码大致由以下两部分组成:
- ASCII 非打印控制字符:ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。
- ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。
转义字符
作用:
- 用于表示一些不能显示出来的ASCII字符。
- 现阶段我们常用的转义字符有:
\n\\\t。
- 现阶段我们常用的转义字符有:
| 转义字符 | 含义 | ASCII码值(十进制) |
|---|---|---|
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\\ | 代表一个反斜线字符\ | 092 |
\' | 代表一个单引号(撇号)字符 | 039 |
2.5 字符串类型
作用:
- 存储字符串。
- 相当于C语言的字符数组。
语法:
string 变量名;string = "abcd";
注意:
- 该类型仅在C++中支持,使用须添加头文件
#include <string>。- 在初始化字符串变量时,用双引号将字符串括起来,不要用单引号,即单个字符视为长度为 \(1\) 的字符串。
string类型大小与其所含的字符长度有关。
字符串的输入和输出
string:使用cin >>或cout <<直接输出即可。
C++语言代码示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "a"; //定义一个string型的变量s1,初始化值为"a"
string s2; //定义一个string型的变量s2
cin >> s2; //读入s2的值
cout << s1 << endl << s2; //输出s1的值后输出一个回车,再输出s2
return 0;
}> 更多支持操作参见string 容器。
C与C++表达字符串的对比:
C语言代码示例:
#include <stdio.h>
int main(){
char a[100] = "lys is a doge"; //C语言可以借助字符数组来达到同样效果
printf("%s\n",a);
return 0;
}C++语言代码示例:
#include <iostream>
#include <string>
using namespace std;
int main(){
string a = "lys is a doge";
cout << a << endl;
return 0;
}2.6 布尔类型
作用:
- 代表
true(真) 或false(假) 的值。- 数值上为 \(1\) 或 \(0\),\(1\) 为真,\(0\) 为假。
语法:
bool 变量名;bool 变量名 = true;bool 变量名 = 1;
注意
- 该类型在
C++语言中支持,在C语言中须添加头文件#include <stdbool.h>使用。bool类型只有两个值,true或false。bool类型大小只有 \(1\) 字节。
布尔类型的输入和输出
bool:cin >>或cout <<直接输出即可。- 输入时,只能输入 \(0\) 或 \(1\)。
C++语言代码示例:
#include <iostream>
using namespace std;
int main(){
bool a = false, b = 1; //定义一个bool型的变量a,b,初始化值为false和1
bool n; //定义一个bool型的变量n
cin >> n; //读入n的值,只能是0或1
cout << a << " " << b << endl; //输出a的值后输出一个空格,再输出b的值和一个回车
cout << n << endl; //输出n的值和一个回车
return 0;
}2.7 常量
作用:
- 用于记录程序中不可更改的数据。
语法:
const 数据类型 常量名 = 初始化值;#define 常量名 常量值
注意:
- 常量是固定值,在程序执行期间不会改变。
- 常量的值在定义后不能被修改,修改则会报错。
C语言代码示例:
#include <stdio.h>
#define b 20 //宏定义的方式定义常量
int main(){
const int a = 10; //const 方式定义常量
/*
a = 20; //a是常量不可修改,否则报错
b = 10; //b是常量不可修改,否则报错
*/
printf("%d %d", a, b);
return 0;
}2.8 关键字及标识符命名规则
关键字:
- 关键字是
C/C++中预先保留的单词(标识符)。- 在定义变量或者常量时候,不要用关键字,否则会产生冲突。
C/C++关键字:
| asm | do | if | return | typedef |
|---|---|---|---|---|
| auto | double | inline | short | typeid |
| bool | dynamic_cast | int | signed | typename |
| break | else | long | sizeof | union |
| case | enum | mutable | static | unsigned |
| catch | explicit | namespace | static_cast | using |
| char | export | new | struct | virtual |
| class | extern | operator | switch | void |
| const | false | private | template | volatile |
| const_cast | float | protected | this | wchar_t |
| continue | for | public | throw | while |
| default | friend | register | true | |
| delete | goto | reinterpret_cast | try |
命名规则
作用:
- C++规定给标识符(变量、常量)命名时,有一套自己的规则,避免和关键字发生冲突。
规则:
- 标识符不能是关键字。
- 标识符只能由字母、数字、下划线组成。
- 第一个字符必须为字母或下划线。
- 标识符中字母区分大小写。
2.9 类型转换
作用:
- 在一些时候(比如某个函数接受
int类型的参数,但传入了double类型的变量),我们需要将某种类型转换成另外一种类型。 C++中类型的转换机制较为复杂,这里主要介绍对于基础数据类型的两种转换:数值提升和数值转换。
2.9.1 数值提升
注意:
- 数值提升过程中,值本身保持不变。
转换规则:
char类型和short类型在进行算术运算时会自动提升为int类型。类似地,unsigned short类型在进行算术运算时会自动提升为unsigned int类型。- 如果有必要(例如向一个接受
long long类型参数的函数中传入int类型的变量),可以将位宽较小的整型变量提升为位宽较大的整型变量(注意符号性需保持不变,若符号性改变,则发生数值转换)。一个常见情况是:位宽较小的变量与位宽较大的变量进行算术运算时,会先将位宽较小的变量提升为位宽较大的变量。 - 位宽较小的浮点数可以提升为位宽较大的浮点数(例如
float类型的变量和double类型的变量进行算术运算时,会将float类型变量提升为double类型变量),其值不变。 bool类型可以提升为整型,false变为 \(0\),而true对应为 \(1\)。
2.9.2 数值转换
注意:
- 数值转换过程中,值可能会发生改变。
转换规则:
如果目标类型为位宽为 \(16\) 的无符号整数类型,则转换结果可以认为是原值取模 \(2^{16}\) 后的结果。例如,将 short 类型的值 \(-1\)(二进制表示为 \(1111~1111~1111~1111\))转换为 unsigned int 类型,其值为 \(65535\)(二进制表示为 \(0000~0000~0000~0000~1111~1111~1111~1111\))。
- 如果目标类型为位宽为 \(x\) 的带符号整数类型,则 一般情况下 ,转换结果可以认为是原值 \(mod~2^x\) 后的结果。例如将
unsigned int类型的值 \(4~294~967~295\)(二进制表示为 \(1111~1111~1111~1111~1111~1111~1111~1111\))转换为short类型,其值为 \(-1\)(二进制表示为 \(1111~1111~1111~1111\))。 - 位宽较大的浮点数转换为位宽较小的浮点数,会将该数舍入到目标类型最接近的值。
- 浮点数转换为整数时,会舍弃浮点数的全部小数部分。
- 整数转换为浮点数时,会舍入到目标类型最接近的值。
- 将其他类型转换为
bool类型时,零值转换为false,非零值转换为true。
3. 运算符
3.1 算术运算符
作用:
- 用于处理四则运算。
算术运算符包括以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加 | 10 + 5 | 15 |
- | 减 | 10 - 5 | 5 |
* | 乘 | 10 * 5 | 50 |
/ | 除 | 10 / 5 | 2 |
% | 取模(取余数) | 10 % 3 | 1 |
++ | 前置递增 | a = 2; b = ++a; | a = 3; b = 3; |
++ | 后置递增 | a = 2; b = a++; | a = 3; b = 2; |
-- | 前置递减 | a = 2; b = --a; | a = 1; b = 1; |
-- | 后置递减 | a = 2; b = a--; | a = 1; b = 2; |
注意:
- 在除法运算中,除数不能为 \(0\)。
- 只有整型变量可以进行取模运算。
- 前置递增先对变量进行
++,再计算表达式,后置递增相反。
算术运算符中的类型转换
作用:
- 当参与运算的变量的数据类型不同时,会发生类型转换,使得两个变量类型一致后再进行运算。
转换规则:
- 先将
char、bool、short等类型提升至int(或unsigned int,取决于原类型的符号性)类型; - 否则,若存在一个变量类型为
double,会将另一变量转换为double类型; - 否则,若存在一个变量类型为
float,会将另一变量转换为float类型; - 否则(即参与运算的两个变量均为整数类型):
- 若两个变量符号性一致,则将位宽较小的类型转换为位宽较大的类型;
- 否则,若无符号变量的位宽不小于带符号变量的位宽,则将带符号数转换为无符号数对应的类型;
- 否则,若带符号操作数的类型能表示无符号操作数类型的所有值,则将无符号操作数转换为带符号操作数对应的类型;
- 否则,将带符号数转换为相对应的无符号类型。
示例:
对于一个整型(int)变量 x 和另一个双精度浮点型(double)y 类型变量:
x / 3的结果将会是整型;x / 3.0的结果将会是双精度浮点型;x / y的结果将会是双精度浮点型;x * 1 / 3的结果将会是整型;x * 1.0 / 3的结果将会是双精度浮点型;
3.2 赋值运算符
作用:
- 用于将表达式的值赋给变量。
赋值运算符包括以下几个符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
= | 赋值 | a = 2; b = 3; | a = 2; b = 3; |
+= | 加等于 | a = 0; a += 2; | a = 2; |
-= | 减等于 | a = 5; a -= 3; | a = 2; |
*= | 乘等于 | a = 2; a *= 2; | a = 4; |
/= | 除等于 | a = 4; a /= 2; | a = 2; |
%= | 模等于 | a = 3; a %= 2; | a = 1; |
对于复合赋值运算符
+=:例如a = 0; a += 2;,等价为a = 0; a = a + 2,结果a = 2,其余复合赋值运算符的运算同理。
3.3 比较运算符
作用:
- 用于表达式的比较,并返回一个真值或假值。
比较运算符有以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
== | 相等于 | 4 == 3 | 0 |
!= | 不等于 | 4 != 3 | 1 |
< | 小于 | 4 < 3 | 0 |
> | 大于 | 4 > 3 | 1 |
<= | 小于等于 | 4 <= 3 | 0 |
>= | 大于等于 | 4 >= 1 | 1 |
注意:
- 要将等于运算符
==和赋值运算符=区分开来,这在判断语句中尤为重要。- 例如:
if (op = 1)与if (op == 1)看起来类似,但实际功能却相差甚远。第一条语句是在对op进行赋值,若赋值为非 0 时为真值,表达式的条件始终是满足的,无法达到判断的作用;而第二条语句才是对op的值进行判断。
- 例如:
3.4 逻辑运算符
作用:
- 用于根据表达式的值返回真值或假值。
逻辑运算符有以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
! | 非 | !a | 如果 a 为假,则 !a 为真;如果 a 为真,则 !a 为假。 |
&& | 与 | a && b | 如果 a 和 b 都为真,则结果为真,否则为假。 |
|| | 或 | a || b | 如果 a 和 b 有一个为真,则结果为真,二者都为假时,结果为假。 |
C++语言代码示例:
#include <iostream>
using namespace std;
int main() {
bool res;
bool a, b;
cin >> a >> b;
res = a && b; // 当 a 与 b 都为真时则 res 为真
cout << res << endl;
res = a || b; // 当 a 或 b 其中一个为真时则 res 为真
cout << res << endl;
res = !a; // 当 a 为假时则 res 为真
cout << res << endl;
return 0;
}3.5 位运算符
作用:
- 位运算就是基于整数的二进制表示进行的运算。
- 由于计算机内部就是以二进制来存储数据,位运算是相当快的。
运算符及运算规则:
| 运算 | 运算符 | 数学符号 | 解释 |
|---|---|---|---|
| 与 | & | & | 只有两个对应位都为 1 时才为 1 |
| 或 | | | | | 只要两个对应位中有一个为 1 时就为 1 |
| 取反 | ~ | ~ | 按位取反,即对应的 0 变为 1,1 变为 0 |
| 异或 | ^ | ⊕ | 只有两个对应位不同时才为 1 |
运算示例:
// 与运算
0 & 0 = 0;
0 & 1 = 0;
1 & 0 = 0;
1 & 1 = 1;
// 或运算
0 | 0 = 0;
0 | 1 = 1;
1 | 0 = 1;
1 | 1 = 1;
// 取反运算
~0 = 1;
~1 = 0;
// 异或运算
0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;
1 ^ 1 = 0;
// 右移运算
n >> k // 表示 n / 2^k
// 左移运算
n << k // 表示 n * 2^k常用模板:
// 求 x 的第 k 位数字
x >> k & 1;
// 求 x 的最后一位 1
x & -x;取反及补码:
取反是对一个数
num进行的位运算,即单目运算。取反暂无默认的数学符号表示,其对应的运算符为
~。它的作用是把 \(num\) 的二进制补码中的 \(0\) 和 \(1\) 全部取反( \(0\) 变为 \(1\), \(1\) 变为 \(0\) )。有符号整数的符号位在~运算中同样会取反。补码:在二进制表示下,正数和 \(0\) 的补码为其本身,负数的补码是将其对应正数按位取反后加一。
例如:
异或运算的逆运算
异或运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即 \(a\oplus b \oplus b = a\)。
例如:
左移与右移
num << i表示将 \(num\) 的二进制表示向左移动 \(i\) 位所得的值。num >> i表示将 \(num\) 的二进制表示向右移动 \(i\) 位所得的值。例如:
注意:
- 移位运算中如果出现如下情况,则其行为未定义:
- 右操作数(即移位数)为负值。
- 右操作数大于或等于操作数类型的位数。
- 对于左移操作,需要确保移位后的结果能被原数的类型容纳,否则行为也是未定义的。对一个负数执行左移操作也未定义。
- 对于右移操作,右侧多余的位将会被舍弃,而左侧较为复杂:对于无符号数,会在左侧补 \(0\) ;而对于有符号数,则会用最高位的数(其实就是符号位,非负数为 \(0\),负数为 \(1\))补齐。
- 位运算的优先级低于算术运算符 (除了取反),而按位与、按位或及异或低于比较运算符。
3.6 逗号运算符
作用:
- 逗号运算符
,可将多个表达式分隔开来,被分隔开的表达式按从左至右的顺序依次计算,整个表达式的值是最后的表达式的值。
注意:
- 逗号表达式的优先级在所有运算符中的优先级是最低的,即再进行多个表达式运算时,在所有其他运算符运算完毕后才执行逗号运算符的运算。
C++代码示例:
#include <iostream>
using namespace std;
int main() {
int res;
res = 1 + 2, 3 + 4, 5 + 6;
// 得到 res 的值为 3 而不是 11,因为赋值运算符 "=" 的优先级比逗号运算符高,先进行了赋值运算才进行逗号运算。
cout << res << endl;
res = (1 + 2, 3 + 4, 5 + 6);
// 若要让 res 的值得到逗号运算的结果,则应将整个表达式用括号提高优先级,此时 res 的值才为 11。
cout << res << endl;
return 0;
}3.7 运算符优先级
作用:
- 不同的运算符在表达式中的优先级不同,会使得表达式按一定的运算规则进行运算,使得运算不发生歧义。
注意:
- 优先级高的运算符先运算,优先级低的运算符后运算(如逗号运算符的例子)。
- 优先级和结合性是编译时概念,与求值顺序无关,后者是运行时概念。
优先级对照:

4. 程序流程结构
C/C++ 支持最基本的三种程序运行结构:
- 顺序结构:程序按顺序执行,不发生跳转。
- 选择结构:依据条件是否满足,有选择地执行相应功能。
- 循环结构:依据条件是否满足,循环多次执行某段代码。
4.1 选择结构
4.1.1 if 语句
作用:
- 执行满足条件的语句。
语法:
基本 if 语句:
if(条件表达式){
// 语句
}C 语言代码示例:
#include <stdio.h>
int main(){
int a; // 定义一个 int 型变量 a
scanf("%d", &a); // 读入 a 的值
if(a != 0){ // 如果 a 的值不为 0
printf("%d\n", a); // 输出 a 的值并输出一个回车
}
// 若 a 的值为 0,则不会执行任何内容,程序正常退出
return 0;
}if...else 语句:
if(条件表达式){
// 语句 1
}
else{
// 语句 2
}C 语言代码示例:
#include <stdio.h>
int main(){
int a; // 定义一个 int 型变量 a
scanf("%d", &a); // 读入 a 的值
if(a != 0){ //如果a的值不为0
printf("%d\n", a); //输出a的值并输出一个换行
}
else{
printf("a的值为0!\n"); //若a的值为0,输出"a的值为0!"并输出一个换行
}
return 0;
}else if 语句:
if(条件表达式1){
//语句1
}
else if(条件表达式2){
//语句2
}
else{
//语句3
}C语言代码示例:
#include <stdio.h>
int main(){
int a; //定义一个int型变量a
scanf("%d", &a); //读入a的值
if(a > 100){ //如果a的值大于100
printf("%d\n", a - 100); //输出a - 100的值并输出一个换行
}
else if(a > 10){
printf("%d\n", a - 10); //输出a - 10的值并输出一个换行
}
else{
printf("%d\n", a); //输出a的值并输出一个换行
}
return 0;
}三目运算符(条件表达式)
语法:表达式1 ? 表达式2 : 表达式3;
解释:
- 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果。
- 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果。
C语言代码示例:
#include <stdio.h>
int main() {
int a; // 定义一个int型变量a
scanf("%d", &a); // 读入a的值
int c = a > 0 ? a : -a; // 若a的值大于0则c = a,否则c = -a
printf("%d\n", c);
return 0;
}4.1.3 switch语句
作用:
- 执行多条件分支语句。
语法:
switch (条件表达式) {
case 结果1: 执行语句1; break;
case 结果2: 执行语句2; break;
// ...
default: 执行语句n; break;
}注意:
switch语句执行时,先求出条件表达式的值,然后根据该值选择相应的case常量,从该case处开始执行。- 其中,条件表达式必须是一个整数类型表达式,而
case后的常量都必须是整数类型的常量。 switch语句中还要根据需求加入break语句进行中断,否则在对应的case被选择之后,接下来的所有case里的语句和default里的语句都会被运行。- 当无对应的
case条件时,会统一执行default的语句。
- 其中,条件表达式必须是一个整数类型表达式,而
C语言代码示例:
#include <stdio.h>
int main() {
// 请给电影评分
// 10 ~ 9 经典
// 8 ~ 7 非常好
// 6 ~ 5 一般
// 5分以下 烂片
int score;
scanf("%d", &score); // 输入分数
switch (score) {
case 10:
case 9:
printf("经典\n");
break;
case 8:
printf("非常好\n");
break;
case 7:
case 6:
printf("一般\n");
break;
default:
printf("烂片\n");
break;
}
return 0;
}4.2 循环结构
4.2.1 while循环语句
作用:
- 满足循环条件,执行循环语句。
语法:
while (条件表达式) {
// 循环体语句
}解释:
- 先判断循环条件表达式,只要循环条件的结果为真,就执行循环语句。
注意:
- 在执行循环语句时,程序必须提供跳出循环的出口,否则会出现死循环。
C语言代码示例:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
int sum = 1;
while(sum <= n) { //当sum小于等于n时执行
printf("%d\n", sum); //输出sum的值和回车
sum++; //执行完一次循环,sum的值加1
}
return 0;
}4.2.2 do...while循环语句
作用:
- 满足循环条件,执行循环语句。
语法:
do {
//语句
} while(条件表达式);解释:
- 先执行一次语句,再判断条件表达式,只要条件表达式的结果为真,就执行循环语句。
注意:
- 与
while的区别在于do...while会先执行一次循环语句,再判断循环条件。
C语言代码示例:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
int sum = 10;
do {
printf("%d\n", sum); //输出sum的值和回车
sum--; //执行完一次循环,sum的值减1
} while(sum >= n); //当sum大于等于n时执行
//若输入的n值大于10,则仍会先执行一次语句,再判断循环条件
return 0;
}4.2.3 for循环语句
作用:
- 先判断条件表达式,满足循环条件,执行循环语句。
语法:
for(初始化条件; 判断条件; 更新) {
//语句
}注意:
for循环中的表达式,要用分号进行分隔。for语句的三个部分中,任何一个部分都可以省略。- 其中,若省略了判断条件,相当于判断条件永远为真。
解释:
- 只要循环条件的结果为真,就执行循环语句。
C语言代码示例:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){ //当i小于等于n时,执行循环体内的语句,执行之后,i的值加1
printf("%d\n", i); //输出i的值和一个回车
}
return 0;
}4.2.4 嵌套循环
作用:
- 在循环体中再嵌套一层循环,解决一些实际问题。
C语言代码示例:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){ //当i小于等于n时,执行循环体内的语句,执行之后,i的值加1
for(int j = 1; j <= i; j ++){//当j小于等于i时,执行循环体内的语句,执行之后,j的值加1
printf("%d ", j); //输出j的值
}
printf("\n"); //执行完内层的循环之后,输出一次回车
}
return 0;
}4.3 跳转语句
4.3.1 break语句
作用:
- 出现在
switch条件语句中,作用是终止case并跳出switch。 - 出现在循环语句中,作用是跳出当前的循环语句。
- 出现在嵌套循环中,跳出最近的内层循环语句。
C语言代码示例1:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){ //当i小于等于n时,执行循环体内的语句,执行后i的值加1
printf("%d ", i); //输出i的值和一个空格
if(i == 10){ //若i的值为10,则终止循环
break;
}
}
//当n小于等于10,会输出1 2 3 ... n
//当n大于10时,会输出1 2 3 ... 10
return 0;
}C语言代码示例2:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){ //当i小于等于n时,执行循环体内的语句,执行后i的值加1
for(int j = 1; j <= i; j ++){ //当j小于等于i时,执行循环体内的语句,执行之后,j的值加1
printf("%d ", j); //输出j的值
if(j == 5){ //j的值为5时,退出该层循环
break;
}
}
}
return 0;
}4.3.2 continue语句
作用:
- 在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
注意:
continue并没有使整个循环终止,而break会跳出循环。
C语言代码示例:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){ //当i小于等于n时,执行循环体内的语句,执行后i的值加1
if(i % 2 == 0){ //当i为偶数时,跳过本次循环,不再执行下面的语句
continue;
}
printf("%d ", i); //输出i的值和一个空格
}
//执行后会输出所有不大于n的所有奇数
return 0;
}5. 数组
5.1 概述
- 所谓数组,就是一个集合,里面存放了相同数据类型 的元素。
- 数组是存放相同类型对象的容器,数组中存放的对象没有名字,而是要通过其所在的位置访问。
- 数组是由连续的内存位置组成的,数组的大小是固定的,不能随意改变数组的长度。
> 程序 = 逻辑 + 数据,数组是存储数据的强而有力的手段。 ——闫学灿
5.2 一维数组
5.2.1 一维数组定义方式
语法:
数据类型 数组名[数组长度];数据类型 数组名[数组长度] = {值1, 值2, ...};数据类型 数组名[] = {值1, 值2, ...};
解释:
数组的声明形如
int a[d],其中,int说明该数组是int类型的数组,里面存放的所有元素均为int类型,a是数组的名字,d是数组中元素的个数。C99之前:元素数量必须是编译时确定的字面量,C99开始我们已经可以用变量来定义数组的大小。示例:
int a[100];
int d1 = 100;
const int d2 = 100;
int a1[d1]; // 正确:C99支持该定义方式
int a2[d2]; // 正确:a2 是一个长度为 100 的数组关于C99
C99标准是 ISO/IEC 9899:1999 - Programming languages -- C 的简称,是C语言的官方标准第二版。
注意:
- 不能将一个数组直接赋值给另一个数组。
- 示例:
int a1[3];
int a2[3] = a1[3]; // 错误
int a3[3];
a3 = a1; // 错误应该尽量将较大的数组定义为全局变量,即将数组声明在 main() 函数外的全局区。因为局部变量会被创建在栈区中,过大(大于栈的大小)的数组会爆栈,进而导致 RE。如果将数组声明在全局作用域中,就会在静态区中创建数组。
#include <stdio.h>
int main(){
int a[100000000]; //数组过大,导致爆栈,运行错误,使得程序异常退出
printf("数组定义成功");
return 0;
}#include <stdio.h>
int a[100000000]; //数组建立在全局区,程序正常运行
int main(){
printf("数组定义成功");
return 0;
}5.2.2 一维数组元素访问
语法:
数组名[下标];
注意:
- 数组的索引(即方括号中的值)从 \(0\) 开始,直到数组长度-1。
- 以一个包含 \(10\) 个元素的数组为例,它的索引为 \(0\) 到 \(9\),而非 \(1\) 到 \(10\)。
- 若访问下标越界,则会发生段错误。
C语言代码示例:
#include <stdio.h>
int main() {
//定义方式1
//数据类型 数组名[元素个数];
int score1[10];
//利用下标赋值
score1[0] = 100;
score1[1] = 99;
score1[2] = 85;
//利用下标输出
printf("%d\n", score1[0]);
printf("%d\n", score1[1]);
printf("%d\n", score1[2]);
//第二种定义方式
//数据类型 数组名[元素个数] = {值1,值2 ,值3 ...};
//如果{}内不足10个数据,剩余数据用0补全
int score2[10] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };
//一个一个输出太麻烦,因此可以利用循环进行输出
for (int i = 0; i < 10; i++){
printf("%d ", score2[i]);
}
printf("\n");
//定义方式3
//数据类型 数组名[] = {值1,值2 ,值3 ...};
int score3[] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };
for (int i = 0; i < 10; i++){
printf("%d ", score3[i]);
}
printf("\n");
return 0;
}C++语言代码示例:
#include <iostream>
using namespace std;
int main() {
//定义方式1
//数据类型 数组名[元素个数];
int score1[10];
//利用下标赋值
score1[0] = 100;
score1[1] = 99;
score1[2] = 85;
//利用下标输出
cout << score1[0] << endl;
cout << score1[1] << endl;
cout << score1[2] << endl;
//第二种定义方式
//数据类型 数组名[元素个数] = {值1, 值2, 值3...};
//如果{}内不足10个数据,剩余数据用0补全
int score2[10] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };
//逐个输出
//cout << score2[0] << " " << endl;
//...
//cout << score2[9] << " " << endl;
//一个一个输出太麻烦,因此可以利用循环进行输出
for (int i = 0; i < 10; i++) {
cout << score2[i] << " ";
}
cout << endl;
//定义方式3
//数据类型 数组名[] = {值1, 值2, 值3...};
int score3[] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };
for (int i = 0; i < 10; i++) {
cout << score3[i] << " ";
}
cout << endl;
return 0;
}5.2.3 一维数组数组名
作用:
- 不同数组名表示不同数组。
- 可以统计整个数组在内存中的长度。
- 可以获取数组在内存中的首地址。
注意:
- 数组名的命名规范与变量名命名规范一致,不要和变量重名。
示例:
#include <iostream>
using namespace std;
int main() {
//数组名用途
//1、可以获取整个数组占用内存空间大小
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
cout << "整个数组所占内存空间为: " << sizeof(a) << endl; //sizeof() 关键字可以返回对应参数的大小
cout << "每个元素所占内存空间为: " << sizeof(a[0]) << endl;
cout << "数组的元素个数为: " << sizeof(a) / sizeof(a[0]) << endl;
//2、可以通过数组名获取到数组首地址
cout << "数组的首元素值为:" << *a << endl; //*a 表示解引用,具体参见指针相关内容
cout << "数组首地址为: " << &a << endl; //&a 表示取数组a的首元素的地址,默认输出为16进制
cout << "数组中第一个元素地址为: " << &a[0] << endl;
cout << "数组中第二个元素地址为: " << (long long)&a[1] << endl; //输出地址为10进制
//arr = 100; 错误,数组名是常量,因此不可以赋值
return 0;
}总结:
- 数组名是常量,不可以赋值。
- 直接打印数组名,可以查看数组所占内存的首地址。
- 对数组名进行
sizeof,可以获取整个数组占内存空间的大小。
5.3 二维数组及多维数组
- 多维数组的实质是数组的数组,即外层数组的元素是数组。
- 二维数组就是在一维数组上,多加一个维度。
- 多维数组定义及访问同二维数组的定义,只是在其基础上再加一个维度。
5.3.1 二维数组定义方式
语法:
数据类型 数组名[行数][列数];数据类型 数组名[行数][列数] = { {数据1, 数据2}, {数据3, 数据4} };数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 };数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 };
> TIPS:以上4种定义方式,第二种更加直观,提高代码的可读性。
解释:
- 数组的声明形如
int Array[3][4],那就指的是定义了一个三行四列的矩阵形状的二维数组,如下图所示。
- 这样的矩阵在内存中是以箭头右边的方式存放的,也就是说实际上我们定义的二维数组在内存中仍然像是一维数组那样连续存储的,可以想象为把一个矩阵一层层伸展铺平。
5.3.2 二维数组元素访问
语法:
- `数组


