自用的C语言笔记
中文乱码问题:增添头文件#include
,并在int main()
开头第一行写上SetConsoleOutputCP(65001);
类型 | 占位符 |
---|---|
整型 | %d |
实型 | %f |
字符 | %c |
字符串 | %s |
作用 | |
---|---|
\r | 光标移到开头 |
\n | 换行 |
\t | 长度可变的空格 |
\t
:制表符,根据前边字母个数在后边补空格,让整体个数达到8或者8的倍数,最少补1个,最多8个作用:打印表格数据的时候可以使其对齐
ASCII可显示字符表
二进制 | 十进制 | 十六进制 | 图形 | 二进制 | 十进制 | 十六进制 | 图形 | 二进制 | 十进制 | 十六进制 | 图形 |
---|---|---|---|---|---|---|---|---|---|---|---|
0010 0000 | 32 | 20 | 空格 | 0100 0000 | 64 | 40 | @ | 0110 0000 | 96 | 60 | ` |
0010 0001 | 33 | 21 | ! | 0100 0001 | 65 | 41 | A | 0110 0001 | 97 | 61 | a |
0010 0010 | 34 | 22 | “ | 0100 0010 | 66 | 42 | B | 0110 0010 | 98 | 62 | b |
0010 0011 | 35 | 23 | # | 0100 0011 | 67 | 43 | C | 0110 0011 | 99 | 63 | c |
0010 0100 | 36 | 24 | $ | 0100 0100 | 68 | 44 | D | 0110 0100 | 100 | 64 | d |
0010 0101 | 37 | 25 | % | 0100 0101 | 69 | 45 | E | 0110 0101 | 101 | 65 | e |
0010 0110 | 38 | 26 | & | 0100 0110 | 70 | 46 | F | 0110 0110 | 102 | 66 | f |
0010 0111 | 39 | 27 | ‘ | 0100 0111 | 71 | 47 | G | 0110 0111 | 103 | 67 | g |
0010 1000 | 40 | 28 | ( | 0100 1000 | 72 | 48 | H | 0110 1000 | 104 | 68 | h |
0010 1001 | 41 | 29 | ) | 0100 1001 | 73 | 49 | I | 0110 1001 | 105 | 69 | i |
0010 1010 | 42 | 2A | * | 0100 1010 | 74 | 4A | J | 0110 1010 | 106 | 6A | j |
0010 1011 | 43 | 2B | + | 0100 1011 | 75 | 4B | K | 0110 1011 | 107 | 6B | k |
0010 1100 | 44 | 2C | , | 0100 1100 | 76 | 4C | L | 0110 1100 | 108 | 6C | l |
0010 1101 | 45 | 2D | - | 0100 1101 | 77 | 4D | M | 0110 1101 | 109 | 6D | m |
0010 1110 | 46 | 2E | . | 0100 1110 | 78 | 4E | N | 0110 1110 | 110 | 6E | n |
0010 1111 | 47 | 2F | / | 0100 1111 | 79 | 4F | O | 0110 1111 | 111 | 6F | o |
0011 0001 | 48 | 30 | 0 | 0101 0000 | 80 | 50 | P | 0111 0000 | 112 | 70 | p |
0011 0001 | 49 | 31 | 1 | 0101 0001 | 81 | 51 | Q | 0111 0001 | 113 | 71 | q |
0011 0010 | 50 | 32 | 2 | 0101 0010 | 82 | 52 | R | 0111 0010 | 114 | 72 | r |
0011 0011 | 51 | 33 | 3 | 0101 0011 | 83 | 53 | S | 0111 0011 | 115 | 73 | s |
0011 0100 | 52 | 34 | 4 | 0101 0100 | 84 | 54 | T | 0111 0100 | 116 | 74 | t |
0011 0101 | 53 | 35 | 5 | 0101 0101 | 85 | 55 | U | 0111 0101 | 117 | 75 | u |
0011 0110 | 54 | 36 | 6 | 0101 0110 | 86 | 56 | V | 0111 0110 | 118 | 76 | v |
0011 0111 | 55 | 37 | 7 | 0101 0111 | 87 | 57 | W | 0111 0111 | 119 | 77 | w |
0011 1000 | 56 | 38 | 8 | 0101 1000 | 88 | 58 | X | 0111 1000 | 120 | 78 | x |
0011 1001 | 57 | 39 | 9 | 0101 1001 | 89 | 59 | Y | 0111 1001 | 121 | 79 | y |
0010 1010 | 58 | 3A | : | 0101 1010 | 90 | 5A | Z | 0111 1010 | 122 | 7A | z |
0011 1011 | 59 | 3B | ; | 0101 1011 | 91 | 5B | [ | 0111 1011 | 123 | 7B | { |
0011 1100 | 60 | 3C | < | 0101 1100 | 92 | 5C | \ | 0111 1100 | 124 | 7C | Vertical bar(无法直接显示用英文代替) |
0011 1101 | 61 | 3D | = | 0101 1101 | 93 | 5D | ] | 0111 1101 | 125 | 7D | } |
0011 1110 | 62 | 3E | > | 0101 1110 | 94 | 5E | ^ | 0111 1110 | 126 | 7E | ~ |
0011 1111 | 63 | 3F | ? | 0101 1111 | 95 | 5F | _ |
类型 | 组成 | 代码 |
---|---|---|
二进制 | 0和1 | 以0b开头 |
十进制 | 0~9 | 不加前缀 |
八进制 | 0~7 | 以0开头 |
十六进制 | 0~9和a~f | 以0x开头 |
以下是代码示例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
// 均输出16
int a = 0b00010000;
printf("%d\n", a);
int b = 16;
printf("%d\n", b);
int c = 020;
printf("%d\n", c);
int d = 0x10;
printf("%d", d);
return 0;
}
数据类型
数据类型 | 对应代码 |
---|---|
整数 | short(2字节) int(4字节) long(4字节) long long(8字节) |
小数 | float(4字节) double(8字节) long double(8字节) |
字符 | char(1字节) (故不能写汉字) |
注意:(只有)整数类型可以和unsigned组合,表示无符号的整数(不是取绝对值),打印时占位符用
%u
不同数据的打印
数据类型 | 占位符 |
---|---|
short int |
%d |
long | %ld |
long long | %lld |
float | %f |
double long double |
%lf |
输出小数时可以控制小数点后位数,例如float类型想控制2位,则使用占位符
%.2f
sizeof的用法
可以利用sizeof来测量每一种数据类型占用字节
用法:printf(“%zu”, sizeof(变量名/数据类型))
键盘录入scanf
(注意scanf不安全,可能会导致内存溢出)
scanf为scanner format的缩写,获取键盘上输入的数据,并赋值给变量
格式为scanf(“%d”, &变量名);
这里需要注意变量名前的&
以下是代码示例1
2
3
4
5
6
7
8
int main()
{
int a;// 首先定义一个变量a
scanf("%d", &a);// 这里对a赋值
printf("%d", a);// 打印变量a
return 0;
}
字符串变量
定义方式
定义方式为:
数据类型 变量名[大小] = 字符串
具体例如:
char str[内存占用大小] = 字符串
内存大小计算方式
类型 | 大小 |
---|---|
英文字母/符号/数字 | 占用1个字节 |
中文 | 占用2个字节 |
结束标记 | 占用1个字节 |
注意:不管有没有字母/符号/数字/中文,都有结束标记。因此内存占用大小均需+1
以下是代码示例1
2
3
4
5
6
7
8
int main()
{
// 这里数字123总共3个字节,加上结束标记1个字节,因此前边填4
char str[4] = "123";
printf("%s", str);
return 0;
}
事实上在str[]
中可以填更大一点,保证能装下字符串大小即可
运算符
运算符分为以下几类:
算数运算符:+ - * / %
自增自减运算符:++ —
赋值运算符:= += -= *= /= %=
关系运算符:== != > >= < <=
逻辑运算符:! && ||
三元运算符:a > b ? a : b
算数运算符
注意:/
代表除法,向下取整;%
代表取余数(运算的数据必须全部都是整数,并且余数的正负和第一个数字保持一致)
在计算过程中会有两个转换:隐式转换和强制转换
数据类型大小排序:double>float>long long>long>int>short>char
隐式转换
定义:在不同数据类型在进行计算、赋值等操作时,会将一个取值范围小的,转成取值范围大的
注意:short/char类型在运算时会先转化为int再进行计算
普通运算
以下是两个示例1
2
3int a = 10;
double b = 1.1;
double c = a + b;// 由于double范围大于int,因此计算结果为double类型
1 | short a = 10; |
字符运算
由于short/char类型在运算时会先转化为int再进行计算,因此字符在相加时会转换为ASCII中的数字形式
以下是代码示例1
2
3
4
5
6
7
8
9
10
int main()
{
char a = 'a';// 首先定义字符
int b = a + 1;// 由于字符相加会转变为int类型,因此定义b为int
printf("%d\n", b);// 字符'a'对应的十进制为97,直接输出的结果为98
printf("%c\n", b);// 如果输出为字符类型,98对应结果为字符'b'
return 0;
}
强制转换
定义:将一个取值范围大的,强制转成取值范围小的
格式:目标数据类型 变量名 = (目标数据类型)需要被强制转化的数据;
例如:首先定义int类型的b
int b = 10;
随后将其强制转变为short类型并赋值给ashort a = (short)b;
注意:强制转换可能导致数据错误
这里给一个计算时的强制转换示例1
2
3
4
5
6
7
8
9
10
11
int main()
{
short a = 10;
short b = 20;
// 由于隐式转换会将short提升为int,因此需要结果为short类型就要进行强制转换
short c = (short)(a + b);// 这里后边也可以直接写a + b,但是最好还是格式规范一些
printf("%d", c);
return 0;
}
自增自减运算符
赋值运算符
同python
关系运算符
符号 | 作用 |
---|---|
== | 判断是否等于 |
!= | 判断是否不等于 |
> | 判断是否大于 |
>= | 判断是否大于等于 |
< | 判断是否小于 |
<= | 判断是否小于等于 |
输出:若为真则输出1,若为假则输出0
逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
&& | 与 | 两边都为真,结果才为真 |
\\\ | 或 | 两边都为假,结果才是假 |
! | 非 | 取反 |
输出:若结果为真则输出1,若结果为假则输出0
ps:逻辑运算会提前停止,例如
&&
在判断时若第一个为假,则直接输出0;||
在判断时若第一个为真,则直接输出1
三元运算符
格式:关系表达式 ? 表达式1 : 表达式2;
例如:
a > b ? a : b;
规则:
1.计算关系表达式的值
2.如果成立,输出表达式1;如果不成立输出表达式2
if和switch
if
规则:如果关系表达式成立则运行语句,反之则运行
else
中的语句,或者不运行
1.
格式如下1
2
3
4if (关系表达式)
{
语句体;
}
2.
格式如下1
2
3
4
5
6
7
8if (关系表达式)
{
语句体A;
}
else
{
语句体B;
}
3.
格式如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17if (关系表达式A)
{
语句体A;
}
else if (关系表达式B)
{
语句体B;
}
else if (关系表达式C)
{
语句体C;
}
···
else
{
语句体N;
}
switch
switch用法如下
1 | switch (关系表达式) { |
执行流程为:
1.计算表达式的值
2.找到对应case的值执行语句,如果没有则执行default后的语句,并且在遇到break时结束
细节补充:
表达式结果和case的值必须为(字符/整数)
case的值不允许重复
default可以放在任何位置
case穿透
如果匹配的case值语句运行完后并没有
break;
,那么会继续执行下方case中代码,直到遇到break
时停止,或者运行到switch结束
循环语句
for循环
格式如下1
2
3
4for (初始化语句; 条件判断语句; 条件控制语句)
{
循环体语句;
}
知道循环次数可以使用这个
while循环
格式如下1
2
3
4
5
6初始化语句;
while (条件判断语句)
{
循环体语句;
条件控制语句;
}
先判断后执行
do while循环
格式如下1
2
3
4
5初始化语句;
do {
循环体语句;
条件控制语句;
} while (条件判断语句);
先执行后判断,至少运行1次
无限循环
这三种类型的无限循环如下
1 | for ( ; ; ) |
1 | while (1) |
1 | do { |
循环的跳转执行
break
break
只能写在switch
或者循环中,在循环中会跳出这一级循环
continue
continue
会结束满足条件的这一次循环,随后接着执行下一次循环,并不会直接跳出这一级循环
goto
goto
可以结合标号,跳转到代码中的任意地方
注意:这个标号和变量不同,可以设置成和变量相同的字母,但可读性不高,不常用
函数
格式如下
1 | 返回值类型 函数名(参数1, 参数2...) |
1 | 变量 = 函数名(参数1, 参数2...) |
return
可以省略不写,若写了则后方不能加返回值
自定义函数在main
函数下边,需要在上方声明
数组
数组:是一种容器,可以用来存储同种数据类型的多个值
用法如下1
数据类型 数组名[长度] = {数据1, 数据2...};
若长度省略,则数据个数就是长度;若不省略,则数据个数需小于等于长度
计算数组长度
使用sizeof
计算数组长度1
int len = sizeof(数组名) / sizeof(int);
二维数组
把多个小数组放到一个大数组里边
用法如下1
2
3
4
5int 数组名[二维数组长度][一维数组长度] = {
{x, x, ...},
{x, x, ...},
{x, x, ...}
};
结合指针存入不同长度的一维数组
例如1
2
3
4
5int arr1[1] = {1};
int arr2[2] = {2, 3};
int arr3[3] = {4, 5, 6};
int* arr[3] = {arr1, arr2, arr3};// 这里存入的是三个数组的内存地址,所以要用int*,理解为指针数组(存放指针的数组)
随机数
普通
首先引用头文件#include
,随后使用函数rand()
但由于C中为伪随机,具有固定算法,因此需要使用srand()
来设置种子
为了使每次的随机数不一样,即设置的种子不一样,因此用时间来作为种子,添加头文件#include
,随后用函数time(NULL)
因此最终代码如下1
2
3
4
5
srand(time(NULL));
int num = rand();// 这里num代表生成的随机数
进阶 1
为了生成固定范围的随机数,可以使用取余
例如:为了生成24~57之间的数,可以先用57-24=33,得到新范围0~33
随后用生成的随机数对34进行取余运算,结果为0~33,随后+24,便得到24~57的随机数
1 | // 在原先随机数基础上进行运算 |
进阶 2
生成随机数时,有概率生成一模一样的数字,为了不重复需要进行判断
最终结合数组,将生成的无重复随机数依次放入数组中
1 | int arr[10] = {0};// 创建包含10个数的空数组 |
内存
获取变量的内存地址
格式如下1
printf("%p", &变量名);
指针
格式:数据类型 * 变量名 = &变量名
上方的
*
为标记
在其他地方使用的*
代表解引用运算符
作用 1
操作函数中的变量
例如交换两个数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
int a = 1;
int b = 2;
swap(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
注意:函数运行结束后会回收内存,如果要保存变量,需要在变量前加static
作用 2
函数返回多个值
作用 3
将函数的结果和计算状态分开
作用 4
方便操作数组和函数
指针的加减
直接加减一个整数,则将指针中记录的内存地址向前或向后移动N个字节,N和数据类型有关
指针之间的运算,可以算出间隔的步长,总字节为步长*数据类型占用字节数
void类型的指针
不同类型的指针之间不能互相赋值,void类型可以接受任意类型指针记录的内存地址,但无法获得变量里边的数据
二级指针
格式:数据类型 指针名 = &指针名
数组指针
两种类型如下1
2
3
4
5
6int arr[] = {1, 2, 3}
// 1.指针获得的是数组的第一个元素的地址,如果进行加减N的话则代表加减数据类型的字节,获得第N-1或N+1个元素的地址
int* p1 = arr;// 数组会退化,这里代表第一个元素的指针
int* p2 = arr + 1;// 进行运算+1代表第二个元素的指针
// 2.指针获得的是数组整体,虽然指针显示的也是第一个元素的地址,但进行加减的话则会加减整个数组的字节
int* p3 = &arr;
补充:在用
sizeof
进行运算的时候,会获得数组的完整字节
二维数组指针
格式一:数据类型 (* 指针名) [一维数组长度] = 二维数组名
格式二:数据类型 指针名 = 二维数组名
示例如下1
2
3
4
5
6
7int arr[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
int(*p)[5] = arr;// p这个指针就是二维数组的指针
1 | int arr1[1] = {1}; |
函数指针
格式:返回值类型 (* 指针名) (形参列表)
可以动态地调用函数
代码示例1
2
3
4
5
6
7
8
9
10// 简单定义一个函数
int add(int a, int b)
{
return a + b;
}
int (*p) (int, int) = add;// p表示函数指针
// 那么可以用指针调用函数
sum = p(1, 2)// sum 即为 3
函数指针数组
假设多个函数形参格式和数量都相等,并且返回值相同,那么可以放在一个数组中
格式:返回值类型 (* 指针数组名[长度]) (形参列表) = {函数1, 函数2, 函数3…}
在调用函数时可以使用(指针数组名[索引数]) (参数)
字符串
字符串实际上是以数组的形式存储的,并且结尾加上\0
例如char str[4] = {‘a’, ‘b’, ‘c’, ‘\0’};
简写为char str[4] = “abc”;
也可以使用指针来存储字符串char* str = “abc”;
。(这种方法定义的会把字符数组放在只读常量区)
只读常量区:内容不可以修改,且里边的字符串可以复用(即如果重复定义相同的字符内容,则此指针地址和最先的定义相同内容的指针地址一样)
字符中常用函数
需要引用头文件
#include
函数名 | 作用 |
---|---|
strlen | 获取字符串长度 |
strcat | 拼接两个字符串 |
strcpy | 复制字符串 |
strcmp | 比较两个字符串 |
strlwr | 将字符串变为小写 |
strupr | 将字符串变为大写 |
strlen
用法:strlen(str);
细节:计算时不包括结束标记\0
,并且一个汉字占用长度为2
strcat
用法:strcat(str1, str2);
原理:把第二个字符串中全部内容拷贝到第一个字符串末尾,因此第一个字符串内容发生改变
条件:第一个字符串可以被修改,并且剩余空间可以容纳第二个字符串
strcpy
用法:strcpy(str1, str2);
原理:把第二个字符串中全部内容替换覆盖第一个字符串全部内容
条件:第一个字符串可以被修改,并且剩余空间可以容纳第二个字符串
strcmp
用法:strcmp(str1, str2);
原理:将两个字符串中内容进行比较,若完全一样则输出0;只要有一个不一样输出非0
细节:要求内容和顺序均完全一样才能输出0
strlwr和strupr
注意:这两个函数均已过时,需要在函数名前加
_
用法:_strlwr(str);
和_strupr(str);
细节:只能转化英文字母的大小写
结构体
结构体在main()
之前定义,但可以在main()
中定义变量
格式如下1
2
3
4
5
6struct 结构体名
{
成员1;
成员2;
...
};
在使用时1
2
3
4
5
6
7
8
9
10
11
12int main()
{
struct 结构体名 变量名;
变量名.成员1 = xxx;
变量名.成员2 = xxx;
...
// 也可以统一进行赋值,如下
struct 结构体名 变量名 = {xxx, xxx...};//注意得按照顺序进行赋值
return 0;
}
若其中有的成员为字符串类型,需要使用strcpy
函数来进行赋值/修改1
2struct 结构体名 变量名;
strcpy(变量名.成员, "字符串内容")
注意:若要将相同结构体的不同变量放入一个数组时写法为
struct 结构体名 数组名[长度] = {变量名1, 变量名2…};
起别名
假设在写使用结构体时觉得在前方加struct
较为麻烦,可以使用如下方法定义结构体
1 | typedef struct 结构体名 |
注意:其中的结构体名可以不写
之后在使用时可以直接用别名
1 | int main() |
注意:若要将相同结构体的不同变量放入一个数组时写法为
别名 数组名[长度] = {变量名1, 变量名2…};
结构体在函数中的应用
如果想在一个函数里边传入一个结构体,可以使用如下
1 | // 首先定义结构体,必须在定义函数之前 |
假设在定义结构体时使用了别名,则可以使代码更简单
1 | // 首先定义结构体,必须在定义函数之前 |
注意:在函数中使用结构体,实际上是新建了一个变量并将原本变量中的数值传递进去,不管在函数中结构体如何变化,都不会影响到原本的数值
进阶
想要真正获取结构体的内容,并进行修改等操作,需要和指针关联,将结构体变量的内存地址传入进去,示例如下
1 | // 以别名为例,如下定义函数 |
结构体嵌套
如果需要在一个结构体中引用另外一个结构体,则需要使用嵌套,示例如下
1 | struct 结构体1 |
在对结构体中的结构体数值进行赋值/修改时如下
1 | int main() |
同理可以通过起别名来使代码更简洁