C语言

前言

​ C语言背景知识:丹尼斯·里奇,由美国贝尔实验室在1978.1.1提出的

​ C语言能做什么:

​ ①操作系统

​ ②驱动开发

​ ③引擎开发

​ ④游戏开发

​ ⑤嵌入式开发

软件选择

​ Visual Studio2022:适合个人以及企业使用。

​ 使用注意:

​ ①安装时选择c++桌面开发即可,并且要带上对应windows系统的SDK。这里我带的是后缀20多的版本

​ ②创建的c文件都要放在源文件当中

​ ③创建c文件时,选择第一个C++文件,但一定要手动把.c后缀带上。、

​ ④一个项目只能有一个main主入口,因此想要执行一个c文件时,把其他的先暂时注释掉。

结构分析

​ 头文件:预处理。即提前要找到需要用的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如#include<stdio.h>  #引入输入输出
#include<math.h> #引入数学计算相关
#include<time.h> #与时间相关
#include<stdlib.h> #标准库,包含伪随机数
int main(){
srand(time(NULL)); //用一个变化的数设置为种子
int num = rand() % 100 + 1; //获取1-100的伪随机数
printf( "%d\n", num) ; //输出打印
return 0;
}




注意:用的时候去http://zh.cppreference.com搜索

​ 主入口:固定main()。若为int main(),然后0表示程序正常结束,非0是异常结束。

执行流程

​ ①编写。创建文件,并编写c代码。

​ ②编译。把文件编译为0和1的文件,给操作系统看的,后缀为.obj

​ ③组合。将.obj和头文件组合起来变成可执行的.exe文件

​ ④运行。

​ 注意:c语言程序是从main入口,从上到下执行。

VS2022使用技巧

1
2
3
①按住ALT进行拉能竖着选择
②字体选择consolas

核心语法

注释

​ 单行注释: // 注释内容

​ 多行注释:/* 注释内容 */

​ 注释快捷键:ctrl + K和ctrl + C组合按键

​ 取消注释键:ctrl + K和ctrl + U组合按键

关键字

​ 概述:被c语言赋予了特殊含义的英文单词

​ 特点:

​ ①关键字的字母全部小写

​ ②常用的代码编辑器,针对关键字有特殊的颜色标记,非常直观.

常量

​ 概述:在程序的执行过程中,其值不能发生改变的数据

​ 分类:

​ ①整型常量:正数、负数、0

​ ②实型常量:所有带小数点的数字

​ ③字符常量:单引号引起来的字母、数字、英文符号。注意只能是这3种

​ ④字符串常量:双引号引起来.

​ 注意:

​ ①实型常量小数点前后为0,可以省略

​ ②科学计数法是实型常量,但是要写E

输出常量

​ printf(参数1,参数2)

​ 参数1:必填。输出内容的最终样式以字符串的形式体现。

​ 参数2,选填。填补的内容

​ 例子:printf(“我的身高:%f米”, 1.73);

格式控制符

​ 即占位符

​ ①整型:%d #decimal

​ ②实型:%f #floating-point

​ ③字符:%c #character

​ ④字符串:%s #string

​ 换行符:

​ ①windows:\r\n #这里\r叫回车符表示回到行首,\n表示换行。这里只写\n也会换行

​ ②mac:\r

​ ③Linux:\n

​ 例子:printf(“我的身高:%f米\n”, 1.73);

​ 制表符:\t。

​ 概述:长度可变的空格。会根据前面字母的个数在后面补空格,让整体的长度达到8或者8的倍数,最少补1个,最多补8个

变量

​ 概述:经常发生改变的变量

​ 定义格式:数据类型 变量名;

​ 注意:

​ ①c语言允许先定义再赋值。

​ ②变量名不允许重复定义

​ ③一条语句可以定义多个变量

​ ④变量在使用之前必须赋值

进制数

​ 二进制:由0和1组成,代码中以0b开头。

​ 十进制:由0~9组成,前面不加任何前缀。

​ 八进制:由0~7组成,代码中以0开头。

​ 十六进制:由09还有af组成,代码中以0x开头。

​ 任意进制转十进制

​ 公式:系数*基数的权次幂相加

​ 系数:就是每一位上的数

​ 基数:当前进制数

​ 权:从右往左, 依次为012345….

​ 十进制转其他进制

​ 除基取余法:不断的除以基数(几进制,基数就是几)得到余数,直到商为0,再将余数倒着拼起来即可。、

数据类型

整数

​ short:短整数,2个字节

​ int:整数,4个字节,默认整数类型

​ long:长整数,赋值要带上L后缀,8个字节

​ long long(C99才提出的):超长整型,后缀是LL,8个字节

​ 注意:可以用sizeof方法,输出占位符为%zu,用来测试各个类型所占字节大小。

​ 有符号整数(也只能和整数搭配)

​ signed:默认情况,可省略。有符号整数。正数,负数。

​ unsigned:无符号整数。打印时用%u占位符。同时取值范围会把负数部分的增加到正数部分。例子unsigned int f = 1;

小数

​ float:单精度小数,以F位后缀。精确小数点6位。4个字节,

​ double:双精度小数。精确小数点15位。默认小数类型,8字节

​ long double:高精度小数,精确小数点18位。window占8字节

​ 注意:

​ ①小数的取值范围都比整型的大,他是以科学计数法存储的

​ ②float保留两位小数的占位符位 %.2f

​ ③double保留两位小数的占位符为 %.2lf

字符

​ char:占一个字节,取值范围就是ASCII码表中的字母,数字,英文符号

取值范围

​ double>float>long long>long>int>short>char

标识符

​ 概述:代码中所有我们自己起的名字。比如函数名字

​ 规则:

​ ①由数字、字母、下划线(_ )组成

​ ②不能以数字开头

​ ③不能是关键字

​ ④区分大小写

​ 软性规则:

​ ①用英文单词,见名知意

​ ②变量名要全部小写

​ ③代码文件名:全部小写,单词之间用下划线隔开,开头可以用数字

键盘录入

scanf

​ 概述:是scanner format的缩写,是C语言提供的一一个函数。

​ 作用:获取用户在键盘上输入的数据,并赋值给变量

​ 使用:scanf(“占位符” , &变量名) #&是取地址符,如果不使用引用类型的,根本无法通过一个函数修改变量的值。

​ 例子:scanf(“%d”, &a)

​ 注意:

​ ①直接用可能会被报错scanf是不安全的,可以用scanf_s代替

​ ②占位符和后面的变量要一一对应。具体比如%d %d,那对应键盘录入也必须中间用空格隔开。间隔符往往用空格和逗号。

​ ③第一个参数中不写换行

运算符

1
2
3
4
5
6
7
算术运算符:+  - 	*	/	%
自增自减运算符:++ --
赋值运算符:= += -= *= /= %=
关系运算符:== != > >= <=
逻辑运算符:&&和||都具有短路效果 !取反没有
三元运算符:a>b?a:b
逗号运算符:,

​ 注意:

​ ①整数计算,结果一定是个整数

​ ②小数计算,结果一定是小数

​ ③整数和小数计算,结果一定是小数

​ ④小数直接参与计算,结果是有可能不精确的

​ ⑤除法不能除以0

​ ⑥取余的时候,运算的数据必须全部都是整数

​ ⑦获取余数的正负,是跟第一个数字保持一致的

自增运算符

​ 它包括了++和–

​ 注意:

​ ①自增和自减放在单独一行,不管在前还是在后是没有影响的

​ ②但后++,如果参与运算,会先按原值进行计算,后自增或自减。

​ 练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
数值拆分
#include<stdio.h>

int main() {
int a,ge,shi,bai;
printf("请输入一个3位数整数:");
scanf_s("%d", &a);
ge = a % 10;
shi = a / 10 % 10;
bai = a / 10 / 10 % 10;
printf("该整数的个位数为:%d\n", ge);
printf("该整数的十位数为:%d\n", shi);
printf("该整数的百位数为:%d\n", bai);

return 0;
}
隐式转换

​ 概述:即把一个取值范围小的,转成取值范围大的。系统会帮我们做

​ 规则:

​ ①取值范围小的,和取值范围大的计算,小的会自动提升为大的,再进行运算

​ ②short char类型的数据在运算的时候,先提升为int, 再进行运算

​ 运算注意事项:

​ ①计算时,数据类型不一样不能直接运算,需要转成一样的,才能运算

​ ②a-z对应ASCII为97-,A-Z对应65-

强式转换

​ 概述:即把一个取值范围大的,转成取值范围小的。需要代码编写

​ 格式:目标数据类型 变量名 = (目标数据类型)被强转的数据;

分支结构

if

switch
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
执行流程:
I.首先计算表达式的值。
II.依次和case后面的值进行比较,如果有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。
III.如果所有的case后面的值和表达式的值都不匹配,就会执行default里面的语句体,然后结束整个switch语句。

格式:
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
default:
语句体n;
break;
}

细节注意:
I. 表达式:计算结果只能为(字符/整数)
II. case:值只能是(字符/整数)的字面量,不能是变量
III. case: 值不允许重复
IV. default: 所有情况都不匹配,执行该处的内容
V. break: 表示中断,结束的意思,结束switch语句

循环结构

for
1
2
3
4
5
技巧:
1.循环的开始条件
2.循环的结束条件
3.要重复执行的事情
4.每次循环结束之后,变量i如何改变i++
while
do{} while(条件)
练习
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
1.给你一个整数n,请你判断该整数是否是2的幕次方
先列举介个,找出规则:任意的一个数字,不断的除以2,最终都会得到数字1
循环的结束条件是什么?(写while循环时,要根据结束条件反过来)
①结果是1的时候,就可以结束了, yes
②结果无法被2再次整除了,也可以结束了,no
// 1.定义一个变量记录数字
int n=100;
// 2.利用循环不断的除以2
//小括号里面写的是:什么情况下,循环可以继续执行,跟上面的推断是要取反的
while(n>1 && n%2 == 0){
n=n / 2;
}
// 3.判断
if(n==1){
printf("yes\n");
}
else{
printf("no\n");
}



2.整数反转
// 1.定义两个变量
int number = 123;
int rev =日;
2.从右边开始,依次获取number每一 位数字, 再次拼到rev当中
//第一次获取:123 % 10=3
//0*10+3=3
//第二次获取:12 % 10=2
//3*10+2=32
//第三次获取: 1 % 10=1
//循环的结束条件: number == 0
//小括号当中书写的是:什么情况下,循环要继续执行
while (number != 0){
//获取number右边的第一位数字
int temp = number % 10;
//去掉刚刚获取的数字
number = number / 10;
//把刚刚获取的数字拼接到rev变量当中
rev = rev*10 + temp;
}
// 3.打印
printf("%d\n", rev);

#在此基础上,加一个判断,可以进行回文数的判定。




4.给定整数n,获取所有小于等于n的质数的数量。
解决问题方法:把复杂问题慢慢分解为许多个简单的问题
①判断一个整数是否是一一个质数
②添加范围1-100
③统计


5.11次方一直加到1010次方之和
//定义一个变量,用于累加最终的结果
long long res = 0;
//外循环:依次表示1 - 10
for(inti=1;i<=10;i++){
//内循环:表示外循环的数字一共要乘几次
// i=1:乘1次
// i= 2:乘2次
//i=3:乘3次
//表示1的1次方+ 2的2次方+ 3的3次方+... + 10的10次方式子当中,每-个选项的单独结果
long long pow = 1; .
for(intj=1;j<=i;j++){
pow = pow* i;
}
//当内循环结束之后,就表示每一一个单独的选项,已经有结果了
//累加的目的:就是把每一个选项的单独结果相加
res = res + pow;
}
//打印
printf("%lld\n", res);


6.找出0~1000之内,符合要求的数字。要求:每一位的数字之和等于15
举例:78,168,1167
#include <stdio.h>
int sum_of_digits(int num) {
int sum = 0;
while (num > 0) {
sum += num % 10;
num /= 10;
}
return sum;
}

int main() {
int num;

printf("0到1000之间的数字,数字和等于15有如下:\n");
for (num = 0; num <= 1000; num++) {
if (sum_of_digits(num) == 15) {
printf("%d ", num);
}
}
printf("\n");
return 0;
}


跳转控制语句

break

​ 概述:直接结束当前循环。跳出当前控制语句

​ 注意:只能写在switch,循环中

continue

​ 概述:结束本次循环,继续下一次循环

​ 注意:只能写在循环中

goto

​ 概述:可以跳转到程序的任意地方

​ 使用:goto 标号; 标号需要标在要跳转语句的前面,如 a : printf(“我到这里了”)

​ 注意:一般只用于跳出循环嵌套。

函数

​ 概述:就是程序中独立的功能。

​ 定义函数的技巧,三个问题:

​ 1.我定义函数,是为了干什么事情?函数体

​ 2.我干这件事情,需要什么才能完成?形参

​ 3.我干完了,调用处是否需要继续使用? 返回值类型。需要继续使用必须写。不需要返回就用void

​ 注意:

​ ①自定义函数一般写在主函数的下方。除非先定义了函数。

数组

​ 概述:是一种容器,可以用来存储同种(隐式转换也是同种)数据类型的多个值

​ 定义:数组类型 数组名[长度]

​ 特点:定义之后长度不可变,

​ 初始化:

​ 静态初始化:数组类型 数组名[长度] = {数据值……}

​ 注意:

​ ①如果数组的长度没有写, 数据值的个数就是数组的长度

​ ②如果数组的长度已经写上,数据值的个数<=长度

​ ③未赋值的部分有默认值。整数为0;小数:为0.0;字符为’\0’;字符串为NULL (什么都没有)

​ ④数组如果在作为函数的参数时,不能给出大小,因为他是作为一个指针变量传过去了。

​ ⑤作为参数传入函数内部,只是把数组的基地址传过去了,从而通过函数会影响数组内部索引的值。

​ 元素访问:数组名称[索引]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
数组与指针
●数组变量本身表达地址,所以
●int a[10]; int *p=a; // 无需用&取地址
●但是数组的单元表达的是变量,需要用&取地址
●a == &a[0]
●[]运算符可以对数组做,也可以对指针做:
●p[0]<==> a[0]
●*运算符可以对指针做,也可以对数组做:
●*a= 25;
●数组变量是const的指针,所以不能被赋值
●int a[]<==> int * const a =.... #const加在指针后面代表指针所存的地址不能被改变

const int a[] = {l,2,3,4,5,6,};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
所以必须通过初始化进行赋值,这个可以用来保护传数组时防止被修改值。

二维数组

​ 初始化:必须得给出列数

1
2
3
4
5
// 动态分配二维数组  
int** array = new int* [M];
for (int i = 0; i < M; ++i) {
array[i] = new int[N];
}

指针

​ 概述:一个指针的变量就是保存地址的变量,也称为指向保存地址的一个变量。

​ 定义:int* p,q = &i,10;(注意q是变量,*只会给p) 或者int *p = &i; #这里叫做 *p是一个int数据类型。

​ 使用:

1
2
3
4
5
6
7
8
9
void f(int *p);		//在被调用的时候得到了某个变量的地址:
int i=0; f(&i); //在函数里面可以通过这个指针访问外面的这个i
注意:通过这种方式可以通过函数内部的操作修改i的值(只有这种引用类型才能)。

获取保存地址的变量
*:它是一个单目运算符,用来访问指针的值所表示的地址上的变量。
● int k = *p;
● *p = k+1;
此时*p整体就可以看作一个整数,但注意前提是p本身是一个指针变量。

​ 练习1

1
2
3
4
5
6
7
8
●交换两个变量的值
void swap(int *pa, int *pb)
{
int t = *pa; #这里*pa和参数的*pa意思不一样,参数那里代表传入一个指针变量,这里代表指针变量pa所存地址的变量
*pa = *pb;
*pb = t;
}
swap(&a,&b) //注意一定要把地址传过去

练习2

1
2
●函数返回多个值,某些值就只能通过指针返回,即通过参数进行返回
●传入的参数实际上是需要保存带回的结果的变量

注意:

​ ①一定不能定义了指针变量,还没有指向任何变量,就开始使用指针。

const

​ 指针与const

​ 格式:int * const p = &a; //代表这是一个一旦赋值了,就不能再次修改地址值的指针变量。

​ 格式2:const int *p = &i; //代表了不能通过指针p来对i赋值。p可以重新指向其他地址,i也可也修改值。

​ 注意:判断哪个被const了的标志是const在*的前面还是后面

&操作符

​ 概述:获得变量的地址,他的操作数必须是变量

指针运算

​ 和运算:对指针变量进行++或者+1等一些和操作,代表的是对地址进行++。其他运算同样是对地址操作。

​ 两个指针运算:他们之间做减法,会给出两个指针中间差了几个量,自动帮你除了对应的字节大小。

​ 注意:

​ ①一定要注意数据类型所占字节数(数组中尤其明显)。

​ ②*p++,其中++的优先级更高,常用于数组类的连续空间操作。在某些cpu他也被认为是汇编语言。

​ ③0地址,他是无意义的,可以理解为就是NULL。也代表指针没有真正初始化。

​ ④不同类型的指针大小都是一样的。但是指向不同类型的指针是不能直接互相赋值的。

​ ⑤void*表示不知道指向什么东西的指针

动态内存分配

​ 背景:如果输入数据时,先告诉你个数,然后再输入,要记录每个数据。C99可以用变量做数组定义的大小,C99之前呢?

1
2
3
4
5
#include.<stdlib.h>	#导入标准库
int *a = (int*)malloc(number*sizeof(int)); #number表示有几个,malloc申请的是字节大小的空间
free(a) #使用完要释放空间,借的什么值,要释放什么值。

注意一定不能弄掉首地址,不然会容易内存泄漏

字符串

​ 概述:他是以字符数组的形态存在的,以’\0’(结束标记)结尾的一段字符

​ 头文件:#include<string.h>

​ 定义格式:char 变量名[大小] = “字符串”; #可以先不写长度

​ 例子:char str[内存占用大小] = “aaa”; #注意这个长度是4

​ 内存占用大小的计算方式:

​ 英文: 1个字母,符号,数字占用一个字节

​ 中文:默认情况下,一个中文占用两个字节

​ 结束标记: 1个字节

​ 字符串常量:char *s = “Hello World!”; #s初始化指向一个字符串常量,并且字符串是不能被修改的。注意他和默认的定义格式不一样,默认的能修改

​ 注意:

​ ①字符串以数组的形式存在,以数组或指针的形式访问。更多的是以指针的形式

​ ②两个相邻字符串,会自动做拼接

​ ③不能用运算符对字符串做运算。通过数组的方式可以遍历字符串

​ ④使用scanf读取字符串是读一个单词,通过回车会空格结束,可以用%7s,表示最大录入7个

​ 内部函数库方法:

​ ①size_t strlen(const char *s):返回s的字符串长度(不包括结尾的0)

​ ②int strcmp(const char *s1, const char *s2):比较两个字符串,返回如下

​ ●0:s1==s2

​ ●1:s1>s2

​ ●-1:s1<s2

​ ③char * strcpy(char *restrict dst, const char *restrict src):把src的字符串拷贝到dst,restrict表明src和dst不重叠(C99),返回dst为了能链起代码来。

​ ④char * strchr(const char *s, int c):返回NULL表示没有找到

​ ⑤char * strrchr(const char *s, int C):从右边开始找,返回NULL表示没有找到

枚举

​ 概述:枚举是一种用户定义的数据类型,它用关键字enum。

​ 定义:enum 枚举类型名字{名字0,….,名字n} ; ‘

结构体

​ 概述:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型

​ 定义:struct 结构体名 {结构体成员列表};

​ 通过结构体创建变量的方式有三种:

​ ①struct 结构体名 变量名

​ ②struct 结构体名 量名 = {成员1值,成员2值..}

​ ③定义结构体时顺便创建变量。即在结构体的}后加入初始化的变量名

​ 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Student{
string name;
int age;
int score;
}
//创建结构体变量方式1
struct Student sl;
//给s1属性赋值,通过.访问结构体变量中的属性
s1.name = "张三”;
s1.age = 18;
sl.score = 100;

//方法2
struct Student s2={”李四”,19,80};

结构体数组

​ 作用:将自定义的结构体放入到数组中方便维护

​ 创建:struct 结构体名 数组名[元素个数] = { {},{},…{}}

结构体指针

​ 作用:通过指针访问结构体中的成员

​ 利用操作符 -> 可以通过结构体指针访问结构体属性

​ 示例:

1
2
3
4
5
//1、创建学生结构体变量
students = {”张三”,18,100 };
//2、通过指针指向结构体变量
student *p = &s;
printf("%s",p->name)
结构体嵌套结构体

​ 作用:结构体中的成员可以是另一个结构体

​ 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
例如:每个老师辅导一个学员, 一个老师的结构体中,记录一个学生的结构体
//学生结构体定义
struct student
{
string name; //姓名
int age; //年龄
int score; //分数
};

//定义老师结构体
struct teacher{
int id; //教师编号
string name; //教师姓名
int age; //年龄
struct student stu; //辅导的学生
};
//创建老师
teacher t;
t.id = 10000;
t.name =”老王”;
t.age = 50;
t.stu.name =”小王”;
t.stu.age = 20;
t.stu.score = 60;
结构体作为参数

​ 作用:将结构体作为参数向函数中传递

​ 传递方式有两种:

​ ①值传递(不会影响原本的值)

​ ②地址传递(会跟着形参的改变而改变) #定义的时候传入struct student *p,使用的时候传入&s1。用->访问内部属性

​ 注意:使用地址传递,可以通过const防止内部属性得到修改。