1. C/C 初识
1.1 第一个C/C 程序
C程序:
代码语言:javascript复制#include <stdio.h>
int main(){
printf("Hello,world!");
return 0;
}
C 程序:
代码语言:javascript复制#include <iostream>
using namespace std;
int main(){
cout << "Hello,world!" ;
return 0;
}
关于 #include
#include
其实是一个预处理命令,意思为将一个文件“放”在这条语句处,被“放”的文件被称为头文件。- 头文件里会包含一些初始化的定义,例如在
C
语言的头文件<stdio.h>
里包含了对printf()
函数的定义,使得在添加头文件#inclued <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 中,程序的返回值不为 0 会导致运行时错误。
- 如果不写 return 语句的话,程序正常结束默认返回值也是 0。
1.2 注释
作用:
- 可以用来解释程序的意思,还可以在让某段代码不执行(但是依然保留在源文件里)
- 在代码中加一些说明和解释,方便自己或其他程序员程序员阅读代码
两种格式:
- 单行注释:
// 描述信息
- 通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明
- 多行注释:
/* 描述信息 */
- 通常放在一段代码的上方,==对该段代码做整体说明
提示:编译器在编译代码时,会忽略注释的内容,注释对程序运行没有影响,C/C 通用该规则
示例:
代码语言:javascript复制#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语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
int a; //定义一个变量a
scanf("%d", &a); //输入a的值
printf("%d", a); //输出a的值
return 0;
}
C 语言代码示例:
代码语言:javascript复制#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语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
float a; //定义一个float型的变量a
double b; //定义一个double型的变量b
scanf("%f %lf", &a, &b); //读入a和b的值
printf("%f %lfn", a, b); //输出a的值后输出一个空格,再输出b的值再输出回车
printf("%.2f %.3lfn", a, b); //输出a的值保留2位小数后输出一个空格,再输出b的值保留3位小数再输出回车
return 0;
}
C 语言代码示例:
代码语言:javascript复制#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字节 | $(0sim 2^{64}-1)$ |
常用的整形输入和输出:
int
:对应scanf()
读入和printf()
输出应为%d
。long long
:对应scanf()
读入和printf()
输出应为%lld
。
C语言代码示例:
代码语言:javascript复制#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 语言代码示例:
代码语言:javascript复制#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.4times 10^{-38}sim 3.4times 10^{38})$ |
double | 8字节 | $(-1.7times10^{-308}sim 1.7times10^{308})$ |
常用的浮点型输入和输出:
float
:对应scanf()
读入和printf()
输出应为%f
。double
:对应scanf()
读入和printf()
输出应为%lf
。printf()
在输出浮点数时,保留x
位小数可以使用%.xf
,%.xlf
。cout <<
在输出浮点数时,保留x
位小数,需要加上头文件#include <iomanip>
,然后cout << setprecision(x)
。
C语言代码示例:
代码语言:javascript复制#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 语言代码示例:
代码语言:javascript复制#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语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
char a; //定义一个char型的变量a
scanf("%c", &a); //读入a值
printf("%cn", a); //输出a的值后输出一个回车
return 0;
}
C 语言代码示例:
代码语言:javascript复制#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 | NUT | 32 | (space) | 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 | DCI | 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 | TB | 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 语言代码示例:
代码语言:javascript复制#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语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
char a[100] = "lys is a doge"; //C语言可以借助字符数组来达到同样效果
printf("%sn",a);
return 0;
}
C 语言代码示例:
代码语言:javascript复制#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
中支持。 bool
类型只有两个值,true
或false
。- bool 类型大小只有 1 字节。
字符串的输入和输出
bool
:cin >>
或cout <<
直接输出即可。- 输入时,只能输入 0 或 1。
C 语言代码示例:
代码语言:javascript复制#include <iostream>
using namespace std;
int main(){
bool a = false, b = 1; //定义一个bool型的变量a,b,初始化值为true和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语言代码示例:
代码语言:javascript复制#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。
数值转换:
数值转换过程中,值可能会发生改变。
- 如果目标类型为位宽为 的无符号整数类型,则转换结果可以认为是原值 后的结果。例如,将 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
,取决于原类型的符号性)类型; - 若存在一个变量类型为
long double
,会将另一变量转换为long double
类型; - 否则,若存在一个变量类型为
double
,会将另一变量转换为double
类型; - 否则,若存在一个变量类型为
float
,会将另一变量转换为float
类型; - 否则(即参与运算的两个变量均为整数类型):
- 若两个变量符号性一致,则将位宽较小的类型转换为位宽较大的类型;
- 否则,若无符号变量的位宽不小于带符号变量的位宽,则将带符号数转换为无符号数对应的类型;
- 否则,若带符号操作数的类型能表示无符号操作数类型的所有值,则将无符号操作数转换为带符号操作数对应的类型;
- 否则,将带符号数转换为相对应的无符号类型。
示例:
对于一个整型( int
)变量 和另一个双精度浮点型( double
)类型变量 :
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 代码示例:
代码语言:javascript复制#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 位运算符
作用
- 位运算就是基于整数的二进制表示进行的运算。
- 由于计算机内部就是以二进制来存储数据,位运算是相当快的。
运算符及运算规则:
代码语言:javascript复制& //与运算
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
常用模板
代码语言:javascript复制//求x的第k位数字
x >> k & 1 ;
//求x的最后一位1
x & -x ;
取反及补码
- 取反是对一个数 num进行的位运算,即单目运算。
- 取反暂无默认的数学符号表示,其对应的运算符为
~
。它的作用是把 num 的二进制补码中的 0和 1全部取反( 0 变为 1, 1 变为 0 )。有符号整数的符号位在~
运算中同样会取反。 - 补码:在二进制表示下,正数和 0 的补码为其本身,负数的补码是将其对应正数按位取反后加一。
- 例如:
异或运算的逆运算
注意:
- 移位运算中如果出现如下情况,则其行为未定义:
- 右操作数(即移位数)为负值。
- 右操作数大于等于左操作数的位数。
- 对于左移操作,需要确保移位后的结果能被原数的类型容纳,否则行为也是未定义的。对一个负数执行左移操作也未定义。
- 对于右移操作,右侧多余的位将会被舍弃,而左侧较为复杂:对于无符号数,会在左侧补 0 ;而对于有符号数,则会用最高位的数(其实就是符号位,非负数为0 ,负数为 1)补齐。
- 位运算的优先级低于算术运算符(除了取反),而按位与、按位或及异或低于比较运算符。
3.6 逗号运算符
作用:
- 逗号运算符
,
可将多个表达式分隔开来,被分隔开的表达式按从左至右的顺序依次计算,整个表达式的值是最后的表达式的值。
注意:
- 逗号表达式的优先级在所有运算符中的优先级是最低的,即再进行多个表达式运算时,在所有其他运算符运算完毕后才执行逗号运算符的运算。
C 代码示例:
代码语言:javascript复制#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 运算符优先级
作用:
- 不同的运算符在表达式中的优先级不同,会使得表达式按一定的运算规则进行运算,使得运算不发生歧义。
注意:
- 优先级高的运算符先运算,优先级低的运算符后运算(如逗号运算符的例子)。
- 优先级和结合性是编译时概念,与求值顺序无关,后者是运行时概念。
优先级对照:
取自C 运算符优先级 - cppreference
4. 程序流程结构
C/C 支持最基本的三种程序运行结构:
- 顺序结构:程序按顺序执行,不发生跳转。
- 选择结构:依据条件是否满足,有选择的执行相应功能。
- 循环结构:依据条件是否满足,循环多次执行某段代码。
4.1 选择结构
4.1.1 if语句
作用:
- 执行满足条件的语句。
语法:
基本 if 语句:
代码语言:javascript复制if(条件表达式){
//语句
}
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
int a; //定义一个int型变量a
scanf("%d", &a); //读入a的值
if(a != 0){ //如果a的值不为0
printf("%dn", a); //输出a的值并输出一个回车
}
//若a的值为0,则不会执行任何内容,程序正常退出
return 0;
}
if...else 语句
代码语言:javascript复制if(条件表达式){
//语句1
}
else{
//语句2
}
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
int a; //定义一个int型变量a
scanf("%d", &a); //读入a的值
if(a != 0){ //如果a的值不为0
printf("%dn", a); //输出a的值并输出一个回车
}
else{
printf("a的值为0!n"); //若a的值为0,输出"a的值为0!"并输出一个回车
}
return 0;
}
else if 语句
代码语言:javascript复制if(条件表达式1){
//语句1
}
else if(条件表达式2){
//语句2
}
else{
//语句3
}
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
int a; //定义一个int型变量a
scanf("%d", &a); //读入a的值
if(a > 100){ //如果a的值大于100
printf("%dn", a - 100); //输出a - 100的值并输出一个回车
}
else if(a > 0 && a <= 100){ //如果a的值大于0且a的值小于等于100
printf("%dn", a 100); //输出a 100的值并输出一个回车
}
else if(a == 0){ //若a的值为0
printf("a的值为0!n"); //输出"a的值为0!"并输出一个回车
}
else{
printf("a的值为小于0!n"); //若a的值不满足以上所有条件,输出"a的值小于0!"并输出一个回车
}
return 0;
}
嵌套 if 语句:
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main(){
int a; //定义一个int型变量a
scanf("%d", &a); //读入a的值
if(a > 0){
if(a > 100){ //如果a的值大于100
printf("%dn", a - 100); //输出a - 100的值并输出一个回车
}
else{
printf("%dn", a 100); //输出a 100的值并输出一个回车
}
}
else if(a == 0){ //若a的值为0
printf("a的值为0!n"); //输出"a的值为0!"并输出一个回车
}
else{
printf("a的值为小于0!n"); //若a的值不满足以上所有条件,输出"a的值小于0!"并输出一个回车
}
return 0;
}
4.1.2 三目运算符
作用:
- 通过三目运算符实现简单的判断。
语法:表达式1 ? 表达式2 :表达式3
解释:
- 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果;
- 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果。
C语言代码示例:
代码语言:javascript复制#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("%dn", c);
return 0;
}
4.1.3 switch语句
作用:
- 执行多条件分支语句。
语法:
代码语言:javascript复制switch(条件表达式){
case 结果1:执行语句1; break;
case 结果2:执行语句2; break;
...
default: 执行语句n; break;
}
注意:
switch
语句执行时,先求出选择句的值,然后根据选择句的值选择相应的标签,从标签处开始执行。- 其中,选择句必须是一个整数类型表达式,而标签都必须是整数类型的常量。
switch
语句中还要根据需求加入break
语句进行中断,否则在对应的case
被选择之后接下来的所有case
里的语句和default
里的语句都会被运行。- 当无对应的
case
条件时,会统一执行default
的语句。
C语言代码示例:
代码语言:javascript复制#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循环语句
作用:
- 满足循环条件,执行循环语句。
语法:
代码语言:javascript复制while(条件表达式){
//语句
}
解释:
- 只要循环条件的结果为真,就执行循环语句。
注意:
- 在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环。
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
int sum = 1;
while(sum <= n){ //当sum小于等于n时执行
printf("%dn", sum); //输出sum的值和回车
sum ; //执行完一次循环,sum的值加1
}
return 0;
}
4.2.2 do...while循环语句
作用:
- 满足循环条件,执行循环语句。
语法:
代码语言:javascript复制do{
//语句
}while(条件表达式);
解释:
- 先执行一次语句,再判断条件表达式,只要条件表达式的结果为真,就执行循环语句。
注意:与 while
的区别在于 do...while
会先执行一次循环语句,再判断循环条件
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
int sum = 10;
do{
printf("%dn", sum); //输出sum的值和回车
sum --; //执行完一次循环,sum的值减1
}while(sum >= n); //当sum大于等于n时执行
//若输入的n值大于10,则仍会先执行一次语句,再判断循环条件
return 0;
}
4.2.3 for循环语句
作用:
- 满足循环条件,执行循环语句。
语法:
代码语言:javascript复制for(初始化条件; 判断条件; 更新){
//语句
}
注意:
for
循环中的表达式,要用分号进行分隔。
解释:
- 只要循环条件的结果为真,就执行循环语句。
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ){ //当i小于等于n时,执行循环体内的语句,执行之后,i的值加1
printf("%dn", i); //输出i的值和一个回车
}
return 0;
}
4.2.4 嵌套循环
作用:
- 在循环体中再嵌套一层循环,解决一些实际问题
C语言代码示例:
代码语言:javascript复制#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:
代码语言:javascript复制#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:
代码语言:javascript复制#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;
}
}
printf("n"); //执行完内层的循环之后,输出一次回车
}
return 0;
}
4.3.2 continue语句
作用:
- 在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
注意:
continue
并没有使整个循环终止,而break
会跳出循环。
C语言代码示例:
代码语言:javascript复制#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;
}
代码语言:javascript复制 #include <stdio.h>
int a[100000000]; //数组建立在全局区,程序正常运行
int main(){
printf("数组定义成功");
return 0;
}
5.2.2 一维数组元素访问
语法:
数组名[下标];
注意:
- 数组的索引(即方括号中的值)从 0开始,直到数组长度-1。
- 以一个包含 10个元素的数组为例,它的索引为 0到 9,而非 1 到 10。
- 若访问下标越界,则会发生段错误。
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
int main() {
//定义方式1
//数据类型 数组名[元素个数];
int score1[10];
//利用下标赋值
score1[0] = 100;
score1[1] = 99;
score1[2] = 85;
//利用下标输出
printf("%dn", score1[0]);
printf("%dn", score1[1]);
printf("%dn", score1[2]);
//第二种定义方式
//数据类型 数组名[元素个数] = {值1,值2 ,值3 ...};
//如果{}内不足10个数据,剩余数据用0补全
int score2[10] = { 100, 90,80,70,60,50,40,30,20,10 };
//逐个输出
// printf("%d ",score2[0]);
// printf("%d ",score2[1]);
// ...
// printf("%d ",score2[9]);
//一个一个输出太麻烦,因此可以利用循环进行输出
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 语言代码示例:
代码语言:javascript复制 #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[1] << " " << 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.2 一维数组数组名
作用:
- 不同数组名表示不同数组。
- 可以统计整个数组在内存中的长度。
- 可以获取数组在内存中的首地址。
注意
- 数组名的命名规范与变量名命名规范一致,不要和变量重名。
示例:
代码语言:javascript复制 #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 Arry[3][4]
,那就指的是定义了一个三行四列的矩阵形状的二维数组,如下图所示。
- 这样的矩阵在内存中是以箭头右边的方式存放的,也就是说实际上我们定义的二维数组在内存中仍然像是一维数组那样连续存储的,可以想象为把一个矩阵一层层伸展铺平。
5.3.2 二维数组元素访问
语法:
数组名[下标1][下标2]
注意:
- 二维数组每一维的下标同一维数组,从 0 开始。
- 访问同一维数组,不能越界访问。
- 在定义二维数组时,如果初始化了数据,可以省略行数。
C 代码示例
代码语言:javascript复制 #include <iostream>
using namespace std;
int main() {
//方式1
//数组类型 数组名 [行数][列数]
int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;
for (int i = 0; i < 2; i ){
for (int j = 0; j < 3; j ){
cout << a[i][j] << " ";
}
cout << endl;
}
//方式2
//数据类型 数组名[行数][列数] = { {数据1,数据2 } ,{数据3,数据4 } };
int a2[2][3] =
{
{1,2,3},
{4,5,6}
};
//方式3
//数据类型 数组名[行数][列数] = { 数据1,数据2 ,数据3,数据4 };
int a3[2][3] = { 1,2,3,4,5,6 };
//方式4
//数据类型 数组名[][列数] = { 数据1,数据2 ,数据3,数据4 };
int a4[][3] = { 1,2,3,4,5,6 };
return 0;
}
5.3.3 二维数组数组名
作用:
- 查看二维数组所占内存空间。
- 获取二维数组首地址。
注意:
- 二维数组名就是这个数组的首地址。
- 对二维数组名进行
sizeof
时,可以获取整个二维数组占用的内存空间大小。
C 代码示例
代码语言:javascript复制 #include <iostream>
using namespace std;
int main() {
//二维数组数组名
int a[2][3] =
{
{1,2,3},
{4,5,6}
};
cout << "二维数组大小: " << sizeof(a) << endl;
cout << "二维数组一行大小: " << sizeof(a[0]) << endl;
cout << "二维数组元素大小: " << sizeof(a[0][0]) << endl;
cout << "二维数组行数: " << sizeof(a) / sizeof(a[0]) << endl;
cout << "二维数组列数: " << sizeof(a[0]) / sizeof(a[0][0]) << endl;
//地址
cout << "二维数组首地址:" << a << endl; //默认输出十六进制地址
cout << "二维数组第一行地址:" << a[0] << endl;
cout << "二维数组第二行地址:" << (long long)a[1] << endl; //输出10进制地址
cout << "二维数组第一个元素地址:" << &a[0][0] << endl;
cout << "二维数组第二个元素地址:" << &a[0][1] << endl;
return 0;
}
5.3.4 二维数组应用案例
考试成绩统计:
案例描述:有三名同学(张三,李四,王五),在一次考试中的成绩分别如下表,请分别输出三名同学的总成绩。
语文 | 数学 | 英语 | |
---|---|---|---|
张三 | 100 | 100 | 100 |
李四 | 90 | 50 | 100 |
王五 | 60 | 70 | 80 |
C 代码示例
代码语言:javascript复制 #include <iostream>
#include <string>
using namespace std;
int main(){
int scores[3][3] =
{
{100,100,100},
{90,50,100},
{60,70,80},
};
string names[3] = { "张三","李四","王五" };
for (int i = 0; i < 3; i ){
int sum = 0;
for (int j = 0; j < 3; j ){
sum = scores[i][j];
}
cout << names[i] << "同学总成绩为: " << sum << endl;
}
return 0;
}
6 函数
6.1 概述
- 将一段经常使用的代码封装起来,减少重复代码。
- 一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。
6.2 函数的声明和定义
函数声明:
- 返回值类型 。
- 函数名。
- 参数列表。
函数定义:
- 函数体内要执行的语句 。
return
表达式。
语法:
代码语言:javascript复制 返回值类型 函数名(参数列表){
函数体语句
return 表达式;
}
关于声明和定义:
- 返回值类型 :即该函数的返回值的数据类型。一个函数可以返回一个值,该值为对应声明函数时的数据类型。
- 函数名:给函数起个名称,后续根据函数名调用对应函数。
- 参数列表:使用该函数时,传入的数据及其传入的数据方式。
- 函数体语句:花括号内的代码,函数内需要执行的语句。
return
表达式: 和返回值类型挂钩,函数执行完后,返回相应的数据。- 如果函数不需要有返回值,则将函数的返回值类型标为
void
,无返回值的函数执行到return ;
语句也会结束执行。 - 如果函数不需要参数,则可以将参数列表置空。
语法示例:
- 声明一个返回值为
int
,函数名为add
的函数,并定义为求两个int
类型的数之和。
//函数
int add(int num1, int num2){ //返回值类型为int,传入两个int型参数num1和num2
int sum = num1 num2; //函数内执行语句
return sum; //将执行语句后的值返回,该值的数据类型为int
}
6.3 函数的调用
作用:
- 在程序运行时使用已经声明和自定义好的函数。
语法:
函数名 (参数);
C语言代码示例:
代码语言:javascript复制#include <stdio.h>
//函数
int add(int num1, int num2){ //返回值类型为int,传入两个int型参数num1和num2
int sum = num1 num2; //函数内执行语句
return sum; //将执行语句后的值返回,该值的数据类型为int
}
int main(){
int a, b;
scanf("%d %d", &a, &b);
int c = add(a, b); //定义一个与add函数返回值相同的int类型的变量c来接收返回值
printf("%dn", c); //输出该值
printf("%d", add(a, b)); //直接输出返回值
return 0;
}
6.4 函数的递归调用
作用:
- 在一个函数内部,也可以调用函数本身。
C语言代码示例:
- 已知编号为 1 的学生年龄为 8 岁,学生编号每增加 1,其年龄增加 2,即编号为 33 的学生年龄为 12 岁。现在输入一个学生的编号,要求利用函数递归求不同编号学生的年龄。
#include <stdio.h>
//函数
int age(int n){
if(n == 1){ //当编号为1时,返回编号为1的学生年龄
return 8;
}
else{
return age(n - 1) 2; //否则编号n - 1,递归到下一层age(n - 1),且年龄 2
}
}
int main(){
int n;
scanf("%d", &n); //输入学生编号
printf("%d", age(n));
return 0;
}
6.5 函数的值传递
实参与形参:
- 函数定义里小括号内称为形参,函数调用时传入的参数称为实参。
- 即实参是从函数体外传入函数的变量。
- 而形参是函数体内的参数列表中用于接收实参信息的变量。
函数的值传递:
- 所谓值传递,就是函数调用时实参将数值传入给形参。
- 通过值传递,如果函数体内的形参数值发生改变,并不会影响函数体外的实参。
- 即值传递时,形参无法修饰实参。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//函数
int add(int num1, int num2){ //实参a将值传递给形参num1,实参b将值传递给num2
num1 = num2; //形参num1的值变为num1 num2
return num1; //将改变值的形参num1的值返回
}
int main(){
int a, b;
scanf("%d %d", &a, &b);
int c = add(a, b); //定义一个与add函数返回值相同的int类型的变量c来接收返回值
printf("%dn", c); //输出该值
printf("%d %dn", a, b); //输出实参a b 的值,发现a和b并未改变
return 0;
}
6.6 函数的地址传递
作用:
- 通过传递函数体外的实参的地址,使得函数体内形参改变时,函数体外的形参同时改变。
语法
- 仅在
C
语言中支持通过引用&
传递,语法较为简单。 C/C
语言均支持指针传递,详情参见指针相关内容。
关于&:
- 在大多数情况下,
&
表示“取地址”。 - 但是在
C
语言中,作为函数的参数传递时,代表“引用”传递。
6.6.1 引用的基本使用
作用:
- 给变量起别名。
语法:
数据类型 &别名 = 原名;
注意:
- 引用仅在
C
语言支持,C
语言可以利用指针达到同样效果,但实际上,引用的内部实现是一个指针常量。
C 语言代码示例:
代码语言:javascript复制#include <iostream>
using namespace std;
int main(){
int a = 10;
int &b = a; //创建a的别名为b 必须初始化
cout << "a = " << a <<endl;
cout << "b = " << b << endl; //b的值同a
//修改a的值
a = 20;
cout << "a = " << a <<endl; //a的值改变
cout << "b = " << b <<endl; //b的值也发生改变
//修改b的值
b = 100;
cout << "a = " << a <<endl; //a的值也发生改变
cout << "b = " << b <<endl; //b的值改变
return 0;
}
6.6.2 引用使用的注意事项
注意事项:
- 引用必须初始化引用的对象。
- 引用在初始化后,不可以改变引用的对象。
C 语言代码示例:
代码语言:javascript复制 #include <iostream>
using namespace std;
int main(){
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a;
//&c = b; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
return 0;
}
6.6.3 引用做函数参数
作用:
- 函数传参时,可以利用引用的技术让形参修饰实参。
C 代码示例
代码语言:javascript复制 #include <iostream>
using namespace std;
int add(int &num1, int &num2){ //a通过引用的方式传递给num1, b也通过引用的方式传递给num2
num1 = num2; //num1的值改变,则函数体外的实参a也将改变
return num1;
}
int main(){
int a, b;
cin >> a >> b;
int c = add(a, b);
cout << c << endl;
cout << a << endl;
return 0;
}
6.7 数组做实参传递
作用:
- 将数组作为函数的实参传入,在函数中对数组中的值的修改,会影响函数外面的数组。
6.7.1 一维数组做实参
语法:
代码语言:javascript复制 // 尽管形式不同,但这三个call函数是等价的
void call(int *a){
语句;
}
void call(int a[]){
语句;
}
void call(int a[10]){
语句;
}
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
void call_1(int a[], int n){
printf("调用call_1输出数组:n");
for(int i = 0; i < n; i ){
printf("%d ",a[i]);
}
printf("n");
}
void call_2(int a[], int n){
printf("调用call_2输出数组:n");
for(int i = 0; i < n; i ){
printf("%d ",a[i]);
a[i] = 0; //改变a[i],nums[i]也同时改变
}
printf("n");
}
int main(){
int n = 10;
int nums[n] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
call_1(nums, n);
printf("调用call_1之后的数组值:n");
for(int i = 0; i < n; i ){
printf("%d ",nums[i]);
}
printf("n");
call_2(nums, n);
printf("调用call_1之后的数组值:n");
for(int i = 0; i < n; i ){
printf("%d ",nums[i]);
}
printf("n");
return 0;
}
6.7.2 二维数组做实参
语法:
代码语言:javascript复制// 多维数组中,除了第一维之外,其余维度的大小必须指定
void call(int (*a)[10]){
语句;
}
void call(int a[][10]){
语句;
}
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
void call(int a[][3], int n, int m){
printf("调用call输出二维数组:n");
for(int i = 0; i < n; i ){
for(int j = 0; j < m; j ){
printf("%d ", a[i][j]);
a[i][j] = 0; //改变a[i][j]的值,nums[i][j]的值也同时改变
}
printf("n");
}
printf("n");
}
int main(){
int n = 2, m = 3;
int nums[2][3] = {{1, 2, 3}, {4, 5, 6}};
call(nums, n, m);
printf("调用call后,输出二维数组:n");
for(int i = 0; i < n; i ){
for(int j = 0; j < m; j ){
printf("%d ", nums[i][j]);
nums[i][j] = 0;
}
printf("n");
}
return 0;
}
7. 指针
7.1 指针的基本概念
作用:
- 可以通过指针间接访问内存。
- 内存编号是从0开始记录的,一般用十六进制数字表示。
- 可以利用指针变量保存地址。
7.2 指针变量的定义和使用
语法:
数据类型 * 变量名;
解释:
- 我们可以通过
&
符号 获取变量的地址,利用指针可以接收这个地址,对指针变量解引用,可以操作指针指向的内存。
指针变量和普通变量的区别
- 普通变量存放的是数据,指针变量存放的是地址。
- 指针变量可以通过
*
操作符,操作指针变量指向的内存空间,这个过程称为解引用。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main(){
//1、指针的定义
int a = 10; //定义整型变量a
//指针定义语法: 数据类型 * 变量名 ;
int * p;
//指针变量赋值
p = &a; //指针指向变量a的地址
printf("&a = %dn", &a); //打印数据a的地址
printf("p = %dn", p); //打印指针变量p
//2、指针的使用
//通过*操作指针变量指向的内存
printf("*p = %dn", *p);
return 0;
}
7.3 指针所占内存空间
解释:
- 指针也是种数据类型,也占有一定的空间。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main(){
int a = 10;
int * p;
p = &a; //指针指向数据a的地址
printf("*p = %dn", *p); //* 解引用
printf("sizeof(*p) = %dn",sizeof(*p));
printf("n");
printf("sizeof(int *) = %dn",sizeof(int *));
printf("sizeof(char *) = %dn",sizeof(char *));
printf("sizeof(float *) = %dn",sizeof(float *));
printf("sizeof(double *) = %dn",sizeof(double *));
return 0;
}
7.4 空指针和野指针
空指针:
- 指针变量指向内存中编号为 0 的空间。
作用:
- 初始化指针变量。
注意:
- 空指针指向的内存不可以访问。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main(){
//指针变量p指向内存地址编号为0的空间
int * p = NULL;
//访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
printf("*p = %dn", *p);
return 0;
}
野指针:
- 指针变量指向非法的内存空间。
注意:
- 空指针和野指针指向的都不是合法申请的空间,不可以访问。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main(){
//指针变量p指向内存地址编号为0x1100的空间
int * p = (int *)0x1100;
//访问野指针运行错误
printf("*p = %dn", *p);
return 0;
}
7.5 const修饰指针
分类:
const
修饰指针——常量指针。const
修饰常量——指针常量。const
即修饰指针,又修饰常量。
技巧:
- 看
const
右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main() {
int a = 10;
int b = 10;
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
printf("const修饰的是指针时,指针指向可以改,指针指向的值不可以更改n");
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
printf("const修饰的是常量时,指针指向不可以改,指针指向的值可以更改n");
//const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
printf("const既修饰指针又修饰常量时,指针指向和指针指向的值都不可以更改n");
return 0;
}
7.6 指针和数组
作用:
- 利用指针访问数组中元素。
注意:
- 指针变量自增并非自增,而是地址偏移。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
int main(){
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int * p = a; //指向数组的指针
printf("下标访问数组首元素: %dn", a[0]);
printf("指针访问数组首元素: %dn", *p);
for (int i = 0; i < 10; i ){
//利用指针遍历数组
printf("%d ", *p);
p ;
}
return 0;
}
7.7 指针和函数
作用:
- 利用指针作函数参数,可以修改实参的值。
注意:
- 指针作为函数参数的传递方式是地址传递。
- 如果不想修改实参,就用值传递,如果想修改实参,就用地址传递。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//值传递,不改变实参
void swap_1(int a, int b){
int t = a;
a = b;
b = t;
printf("调用swap_1: a = %d b = %dn", a, b);
}
//地址传递,修改实参
void swap_2(int *a, int *b){
int t = *a;
*a = *b;
*b = t;
printf("调用swap_2: a = %d b = %dn", *a, *b);
}
int main(){
int a, b;
a = 10, b = 20;
swap_1(a, b);
printf("调用swap_1后,a = %d b = %dn", a, b);
swap_2(&a, &b);
printf("调用swap_2后,a = %d b = %dn", a, b);
return 0;
}
7.8 指针、数组、函数组合应用
案例描述:
- 封装一个函数,利用冒泡排序,实现对整型数组的升序排序。
注意:
- 当数组名传入到函数作为参数时,被退化为指向首元素的指针。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//冒泡排序函数
void bubbleSort(int * num, int len){ //int * num 也可以写为int num[]
for (int i = 0; i < len - 1; i ){
for (int j = 0; j < len - 1 - i; j ){
if (num[j] > num[j 1]){
int temp = num[j];
num[j] = num[j 1];
num[j 1] = temp;
}
}
}
}
//打印数组函数
void printArray(int num[], int len){
for (int i = 0; i < len; i ){
printf("%d ", num[i]);
}
printf("n");
}
int main() {
int a[10] = {4, 3, 6, 9, 1, 2, 10 ,8 ,7 ,5};
int len = sizeof(a) / sizeof(int);
bubbleSort(a, len);
printArray(a, len);
printf("调用以上函数后,原数组的值:n");
for(int i = 0; i < 10; i ) printf("%d ", a[i]);
return 0;
}
8 结构体
8.1 结构体基本概念
作用:
- 结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
- 在
C
语言中的struct
可以看做是一系列称为成员元素的组合体,而在C
语言中struct
被扩展为类似class
的类说明符。
8.2 结构体定义和使用
语法:
代码语言:javascript复制 //定义结构体类型
struct 结构体名{
数据类型 变量名1;
数据类型 变量名2;
...
};
//创建变量方式
//方式1
struct 结构体名{
数据类型 变量名1;
数据类型 变量名2;
...
}变量名;
//方式2
struct 结构体名 变量名;
//方式3
struct 结构体名 变量名 = {值1, 值2, ...};
解释:
代码语言:javascript复制 //定义结构体类型
struct point{
int x;
int y;
};
//创建变量方式
//方式1
struct point{
int x;
int y;
}a;
//方式2
struct point b;
//方式3
struct point c = {1,2};
- 上例中定义了一个名为
point
的结构体,两个成员元素x, y
,类型都为int
。 - 可以在定义结构体类型时直接创建变量,如第一种方式。
- 可以在创建变量时进行初始化,如
struct point c = {1,2};
创建了一个point
类型的变量c
,其数据成员x = 1, y = 2
。
注意:
- 定义结构体时的关键字是
struct
,不可省略。 - 创建结构体变量时,关键字
struct
可以省略,例如上述例子中的创建变量方式 3,使用语句point c = {1,2};
亦合法。 - 结构体变量利用操作符
.
访问成员。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//结构体定义
struct student{
//成员列表
char name[100]; //姓名
int age; //年龄
int score; //分数
}stu3; //结构体变量创建方式3
int main(){
//结构体变量创建方式1
student stu1; //struct 关键字可以省略
//结构体变量创建方式2
struct student stu2 = {"lys", 14, 100};
printf("姓名:%s 年龄:%d 分数:%dn",stu2.name, stu2.age, stu2.score);
return 0;
}
8.3 结构体数组
作用:
- 将自定义的结构体放入到数组中方便维护。
语法:
代码语言:javascript复制 //定义结构体类型
struct 结构体名{
数据类型 变量名1;
数据类型 变量名2;
...
}数组名[数组长度];
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//结构体定义
struct student{
//成员列表
char name[100]; //姓名
int age; //年龄
int score; //分数
};
int main() {
//结构体数组
student stu[3]=
{
{"lys", 14, 99 },
{"ccf", 14, 99 },
{"hfcj", 99, 0}
};
for (int i = 0; i < 3; i ){
printf("姓名:%s 年龄:%d 成绩:%dn", stu[i].name, stu[i].age, stu[i].score);
}
return 0;
}
8.4 结构体指针
作用:
- 通过指针访问结构体中的成员。
语法:
- 利用操作符
->
可以通过结构体指针访问结构体属性。
-> 与 . 的区别:
->
是指向结构体成员运算符,.
是断点符号,不属于运算符。->
所指向的是结构体或对象的首地址,.
所指向的是结构体或对象。p -> name
等价于(*p).name
,两种表示等价。
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//结构体定义
struct student{
//成员列表
char name[100]; //姓名
int age; //年龄
int score; //分数
};
int main(){
student stu = {"lys", 14, 0};
student * p = &stu;
p -> score = 100; //指针通过 -> 操作符可以访问成员
printf("姓名:%s 年龄:%d 成绩:%dn", p -> name, p -> age, p -> score);
return 0;
}
8.5 结构体嵌套结构体
作用:
- 结构体中的成员可以是另一个结构体
C语言代码示例:
代码语言:javascript复制 #include <stdio.h>
//学生结构体定义
struct student{
//成员列表
char name[100]; //姓名
int age; //年龄
int score; //分数
};
//教师结构体定义
struct teacher{
//成员列表
int id; //职工编号
char name[100]; //教师姓名
int age; //教师年龄
student stu; //子结构体 学生
};
int main(){
struct teacher t1 = {114514, "野兽仙贝", 24, {"lys", 14, 100}};
t1.stu.age = 19;
t1.stu.score = 19;
printf("教师职工编号: %d 教师姓名:%s 教师年龄:%dn",t1.id, t1.name, t1.age);
printf("该教师的学生姓名:%s 学生年龄:%d 学生分数:%dn",t1.stu.name, t1.stu.age, t1.stu.score);
return 0;
}
8.6 结构体做函数参数
作用:
- 将结构体作为参数向函数中传递
传递方式:
- 值传递,不修改实参。
- 地址传递,修改实参。
示例:
代码语言:javascript复制 #include <stdio.h>
//学生结构体定义
struct student{
//成员列表
char name[100]; //姓名
int age; //年龄
int score; //分数
};
//值传递
void call_1(student stu){
stu.age = 24;
printf("调用call_1时stu的值:n");
printf("姓名:%s 年龄:%d 分数:%dn", stu.name, stu.age, stu.score);
printf("n");
}
//地址传递
void call_2(student *stu){
stu -> age = 24;
printf("调用call_2时stu的值:n");
printf("姓名:%s 年龄:%d 分数:%dn", stu -> name, stu -> age, stu -> score);
printf("n");
}
int main(){
student stu = {"lys",14,100};
//值传递
call_1(stu);
printf("调用call_1后stu的值:n");
printf("姓名:%s 年龄:%d 分数:%dn", stu.name, stu.age, stu.score);
printf("n");
//地址传递
call_2(&stu);
printf("调用call_2后stu的值:n");
printf("姓名:%s 年龄:%d 分数:%dn", stu.name, stu.age, stu.score);
printf("n");
return 0;
}
8.7 结构体排序
案例描述:
给出五名学生的学号、姓名、总成绩,按照成绩从大到小排序,若成绩相同则学号小的优先。
注意:
- 借助
C
语言中的sqrt()
函数需要添加头文件#include <algorithm>
。 - 用法参阅。
C 语言代码示例:
代码语言:javascript复制#include <iostream>
#include <algorithm>
using namespace std;
struct student{
int num;
string name;
int sum;
};
bool cmp(student &s1, student &s2){
if(s1.sum == s2.sum) return s1.num < s2.num;
return s1.sum > s2.sum;
}
int main(){
student stu[5] = {
{202111, "Aa", 88},
{202112, "Bb", 92},
{202113, "Cc", 73},
{202114, "Dd", 92},
{202115, "Ee", 89},
};
sort(stu, stu 5, cmp);
for(int i = 0; i < 5; i ){
cout << stu[i].num << " " << stu[i].name << " " << stu[i].sum << endl;
}
return 0;
}