中文乱码问题:增添头文件#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
#include <stdio.h>
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
#include <stdio.h>
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
#include <stdio.h>
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
3
int a = 10;
double b = 1.1;
double c = a + b;// 由于double范围大于int,因此计算结果为double类型

1
2
3
short a = 10;
short b = 20;
int c = a + b;// short类型在运算时会转化为int

字符运算

由于short/char类型在运算时会先转化为int再进行计算,因此字符在相加时会转换为ASCII中的数字形式

以下是代码示例

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

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类型并赋值给a short a = (short)b;

注意:强制转换可能导致数据错误

这里给一个计算时的强制转换示例

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

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
4
if (关系表达式)
{
语句体;
}

2.

格式如下

1
2
3
4
5
6
7
8
if (关系表达式)
{
语句体A;
}
else
{
语句体B;
}

3.

格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (关系表达式A)
{
语句体A;
}
else if (关系表达式B)
{
语句体B;
}
else if (关系表达式C)
{
语句体C;
}
···
else
{
语句体N;
}

switch

switch用法如下

1
2
3
4
5
6
7
8
9
10
11
12
switch (关系表达式) {
case 数值1:
语句体1;
break;
case 数值2:
语句体2;
break;
···
default :
语句体N;
break;
}

执行流程为:

1.计算表达式的值

2.找到对应case的值执行语句,如果没有则执行default后的语句,并且在遇到break时结束

细节补充:

表达式结果和case的值必须为(字符/整数

case的值不允许重复

default可以放在任何位置

case穿透

如果匹配的case值语句运行完后并没有break;,那么会继续执行下方case中代码,直到遇到break时停止,或者运行到switch结束

循环语句

for循环

格式如下

1
2
3
4
for (初始化语句; 条件判断语句; 条件控制语句)
{
循环体语句;
}

知道循环次数可以使用这个

while循环

格式如下

1
2
3
4
5
6
初始化语句;
while (条件判断语句)
{
循环体语句;
条件控制语句;
}

先判断后执行

do while循环

格式如下

1
2
3
4
5
初始化语句;
do {
循环体语句;
条件控制语句;
} while (条件判断语句);

先执行后判断,至少运行1次

无限循环

这三种类型的无限循环如下

1
2
3
4
for ( ; ; )
{
循环体语句;
}
1
2
3
4
while (1)
{
循环体语句;
}
1
2
3
do {
循环体语句;
} while (1);

循环的跳转执行

break

break只能写在switch或者循环中,在循环中会跳出这一级循环

continue

continue会结束满足条件的这一次循环,随后接着执行下一次循环,并不会直接跳出这一级循环

goto

goto可以结合标号,跳转到代码中的任意地方

注意:这个标号和变量不同,可以设置成和变量相同的字母,但可读性不高,不常用

函数

格式如下

1
2
3
4
5
返回值类型 函数名(参数1, 参数2...)
{
函数体;
return 返回值;
}
1
变量 = 函数名(参数1, 参数2...)

void</code>开头没返回值,return可以省略不写,若写了则后方不能加返回值

自定义函数在main函数下边,需要在上方声明

数组

数组:是一种容器,可以用来存储同种数据类型的多个值

用法如下

1
数据类型 数组名[长度] = {数据1, 数据2...};

若长度省略,则数据个数就是长度;若不省略,则数据个数需小于等于长度

计算数组长度

使用sizeof计算数组长度

1
int len = sizeof(数组名) / sizeof(int);

二维数组

把多个小数组放到一个大数组里边

用法如下

1
2
3
4
5
int 数组名[二维数组长度][一维数组长度] = {
{x, x, ...},
{x, x, ...},
{x, x, ...}
};

结合指针存入不同长度的一维数组

例如

1
2
3
4
5
int 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
#include <stdlib.h>
#include <time.h>

srand(time(NULL));
int num = rand();// 这里num代表生成的随机数

进阶 1

为了生成固定范围的随机数,可以使用取余

例如:为了生成24~57之间的数,可以先用57-24=33,得到新范围0~33

随后用生成的随机数对34进行取余运算,结果为0~33,随后+24,便得到24~57的随机数

1
2
// 在原先随机数基础上进行运算
int num = rand() % 34 + 24;// 代表24~57之间的随机数

进阶 2

生成随机数时,有概率生成一模一样的数字,为了不重复需要进行判断

最终结合数组,将生成的无重复随机数依次放入数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int arr[10] = {0};// 创建包含10个数的空数组
srand(time(NULL));// 设置种子
for (int i = 0; i < 10; )// 原本需进行10次循环,但由于可能有重复,因此i在确定没有重复后再进行自加
{
int num = rand() % 10 + 1;// 生成1~10之间的10个随机数,刚好填满空数组,便于检查是否重复
int flag = 0;// 设置标记数字为0
for (int j = 0; j < i; j++)// 这个循环是将这次生成的随机数,分别与目前已经填入数组中的随机数进行比较
{
if (arr[j] == num)
{
flag = 1;// 判断是否和之前有重复,如有重复,则更改标记数字为1
break;// 同时跳出本次循环,进行下一次循环,减少运算量
}
}
if (flag == 0)
{
arr[i] = num;// 判断标记数字是否满足(如果为0则没有重复,如果为1则有重复)
i++;// 确定没有重复后,i再进行自加
}
}

内存

获取变量的内存地址

格式如下

1
printf("%p", &变量名);

指针

格式:数据类型 * 变量名 = &变量名

上方的*为标记

在其他地方使用的*代表解引用运算符

作用 1

操作函数中的变量

例如交换两个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void 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
6
int 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
7
int arr[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};

int(*p)[5] = arr;// p这个指针就是二维数组的指针

1
2
3
4
5
6
7
int arr1[1] = {1};
int arr2[2] = {2, 3};
int arr3[3] = {4, 5, 6};

int* arr[3] = {arr1, arr2, arr3};// 这里存入的是三个数组的内存地址,所以要用int*,理解为指针数组(存放指针的数组)

int** p = arr;// p这个指针就是二维数组的指针

函数指针

格式:返回值类型 (* 指针名) (形参列表)

可以动态地调用函数

代码示例

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
6
struct 结构体名
{
成员1;
成员2;
...
};

在使用时

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
struct 结构体名 变量名;
变量名.成员1 = xxx;
变量名.成员2 = xxx;
...

// 也可以统一进行赋值,如下
struct 结构体名 变量名 = {xxx, xxx...};//注意得按照顺序进行赋值

return 0;
}

若其中有的成员为字符串类型,需要使用strcpy函数来进行赋值/修改

1
2
struct 结构体名 变量名;
strcpy(变量名.成员, "字符串内容")

注意:若要将相同结构体的不同变量放入一个数组时写法为struct 结构体名 数组名[长度] = {变量名1, 变量名2…};

起别名

假设在写使用结构体时觉得在前方加struct较为麻烦,可以使用如下方法定义结构体

1
2
3
4
5
6
typedef struct 结构体名
{
成员1;
成员2;
...
} 别名;

注意:其中的结构体名可以不写

之后在使用时可以直接用别名

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
别名 变量名;
变量名.成员1 = xxx;
变量名.成员2 = xxx;
...

// 也可以统一进行赋值,如下
别名 变量名 = {xxx, xxx...};//注意得按照顺序进行赋值

return 0;
}

注意:若要将相同结构体的不同变量放入一个数组时写法为别名 数组名[长度] = {变量名1, 变量名2…};

结构体在函数中的应用

如果想在一个函数里边传入一个结构体,可以使用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 首先定义结构体,必须在定义函数之前
struct 结构体名
{
成员1;
成员2;
...
};

void 函数名(struct 结构体名 形参名)
{
// 随后可以用调用结构体中的内容
形参名.成员
}

int main()
{
// 定义一个结构体变量
struct 结构体名 变量名;
变量名.成员1 = xxx;
变量名.成员2 = xxx;

// 使用函数时如下
函数名(变量名);

return 0;
}

假设在定义结构体时使用了别名,则可以使代码更简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 首先定义结构体,必须在定义函数之前
typedef struct 结构体名
{
成员1;
成员2;
...
} 别名;

void 函数名(别名 形参名)// 这个形参名就代表变量名
{
// 随后可以用调用结构体中的内容
形参名.成员
}

int main()
{
// 定义一个结构体变量
别名 变量名;
变量名.成员1 = xxx;
变量名.成员2 = xxx;

// 使用函数时如下
函数名(变量名);

return 0;
}

注意:在函数中使用结构体,实际上是新建了一个变量并将原本变量中的数值传递进去,不管在函数中结构体如何变化,都不会影响到原本的数值

进阶

想要真正获取结构体的内容,并进行修改等操作,需要和指针关联,将结构体变量的内存地址传入进去,示例如下

1
2
3
4
5
6
7
8
9
// 以别名为例,如下定义函数
void 函数名(别名* 指针变量名)
{
// 在对结构体中数值进行操作时如下,前边为一个整体需要带括号
(*指针变量名).成员
}

// 使用函数时如下
函数名(&变量名);// 传入地址

结构体嵌套

如果需要在一个结构体中引用另外一个结构体,则需要使用嵌套,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct 结构体1
{
成员1_1;
成员1_2;
...
};// 注意这里需要放在另一个结构体中的结构体要提前定义

struct 结构体2
{
成员2_1;
成员2_2;
...
struct 结构体1 变量名1;
};

在对结构体中的结构体数值进行赋值/修改时如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
struct 结构体2 变量名2;
// 如果成员直接属于结构体2,可以直接进行操作
变量名2.成员2_1 = xxx;
...
// 如果成员属于结构体1,需要先在结构体2中找到结构体1
变量名2.变量名1.成员1_1 = xxx;
...

// 也可以统一进行赋值,如下
struct 结构体2 变量名 = {xxx, xxx...{xxx, xxx...}};
// 这里注意在赋值到结构体1中内容时需要加大括号{},并且得按照顺序进行赋值

return 0;
}

同理可以通过起别名来使代码更简洁