一、前言
-
什么是程序?
-
计算机程序:计算机PC、嵌入式领域
-
程序:数据结构和算法(沃斯
- 数据结构:对数据的描述(数据的类型和组织方式
- 算法:对操作的描述,嵌入式主要是编写逻辑型和数学型算法(算法工程师研发数据处理大模型
-
进程/任务:跑起来的程序叫做进程,动态描述
-
-
程序怎么来的
编写、编译(预处理、编译、汇编、链接)、执行
-
计算机语言
- 机器语言——光电输入机读取穿孔纸带信息
- 符号语言——汇编
- 高级语言——if、else…
C语言面向过程
C++、java、PHP面向对象,类
通俗解释:洗衣服的例子 -
学习过程
- 看视频不要倍速
- 视频中的代码(理解、对着打、默打、积累编译错误提示、错误代码不要放弃不断调试、多总结[ csdn 写博客] )
- 算法bug、目的是能编程 做东西、考核标准:链表嵌套做学生成绩管理系统
关于CMD窗口的常用命令:
cls // 清屏
Ctrl + C // 终止命令
gcc demo.c -g // 编译
gdb a.exe // 调试
r // 运行
q // 退出
二、初识
-
IDE编辑器
Notepad++编辑 CMD窗口运行
CMD:①gcc 文件名.c 编译 ②a.exe 运行
stdio.h 声明了printf 预处理替换了include的内容
-
数据的表现形式
-
变量四要素: 变量名、变量值、变量类型和存储单元( 存储位置 )
-
命名:字母数字下划线 区分大小写 不要用 int a;
C语言命名变量方法:匈牙利、驼峰法、帕斯卡、下划线法驼峰命名法 变量名顾名思义 变量secondsPerYear 函数Second_Per_Year 为了防止和系统变量冲突会以 _ 开头命名 _students
-
C语言数据类型:
各个类型数据含义
-
基本类型:整型、字符型、浮点型
整型和字符型可以通过ASCII码联系起来, 字符型可以用整型表示
int data1 = 10; char data1 = 'a'; // 必须加单引号 // ASCII码 A——65 a——97
在Linux或者64位操作系统 int 和 float 4个字节 char 1个字节 1个字节==8位
单片机中 int 2个字节
-
输入输出
printf 格式控制和输出列表 %d %c %s 字符串 %f %x (0x )
scanf 格式控制和地址列表 &a 一定要加 &
scanf(“%d%d%d”, a, b, c); 输入的时候回车或者空格隔开 原样输入.
数据输出格式控制符
bool类型也用%d输出scanf格式控制不要加逗号以及其他控制字符,多个变量输入用多个scanf
scanf输入多个整型数据 空格 回车可以做分隔符, scanf输入多个char数据 空格 回车 tab 不能当分隔符
scanf混合输入%d%c%f, 输入的时候一定要对应类型
其他输入输出:putchar() getchar() puts() gets()
tips :puts和printf区别:puts自动添加换行符,而printf支持花样输出
代码太长了换行,提高代码可读性代码分行显示 反斜杠 \
-
大小写字母转换
getchar(); // 从键盘上获得一个字符 putchar(getchar()); // 打印从键盘上获得的字符
-
三、流程控制
-
控制语句:
if(){---}else{---} // 条件语句 for(){---} // 循环语句 while(){---} // 循环语句 do{---}while() // 循环语句 continue // 结束本次循环语句 break // 终止switch或者循环语句 switch // 多分支选择语句 return // 从函数返回语句
for(;;); // 死循环
-
关于switch:
switch判断条件为整型,可以把数据通过 加减乘除 取余等运算得到整型条件
// switch switch( lucheng / 250 ){ case 0: puts("zhekou1"); break; case 1: case 2: case 3: puts("zhekou2"); break; default: puts("zhekou3"); }
遇到死循环Ctrl + C结束
-
两个小算法
-
最大公约数:如何计算最大公约数?
常用质因数分解法、欧几里得法、相减法和辗转相减法
-
最小公倍数
利用最大公因数求最小公倍数,因为两个自然数的最大公因数与它们的最小公倍数的乘积,等于这两个数的积。
// 最大公约数和最小公倍数 #include<stdio.h> #include<math.h> int main(){ int data1, data2; // data_a = 36; // data_b = 36; puts("请输入两个数:"); scanf("%d%d", &data1, &data2); int data_a, data_b; int data_r; int data_d; // 使得data_a > data_b if(data1 > data2){ data_a = data1; data_b = data2; }else{ data_a = data2; data_b = data1; } /* // 辗转相除法 while(1){ data_r = data_a%data_b; if(data_r == 0){ printf("最大公约数为:%d", data_b); break; } data_a = data_b; data_b = data_r; } */ // 辗转相减法 while(1){ data_d = data_a - data_b; if(data_d%data_b == 0){ printf("最大公因数为:%d\n", data_b); break; } data_a = data_b; data_b = data_d%data_b; } // 求最小公倍数,利用最大公因数 int data_multy; // 最小公倍数 data_multy = data1*data2/data_b; printf("最小公倍数为:%d", data_multy); return 0; }
-
四、数组
-
数组定义
int a[10] = {0}; int num = 0; printf("地址:%p, 数据:%d", &a[num], a[num]); // &取址符
-
数组初始化
int a[] = {1, 2, 3, 4, 5}; // 自动判断出长度为5 int b[5] = {0}; // 初始化为0
-
sizeof关键字,能计算数据的内存空间大小,也能求数组的长度
-
两个排序法:冒泡和简单选择排序法
冒泡:排序len-1轮,每轮排序len-1-i次
选择:排序len-1轮,最大的在前,从i+1排到最后一个
-
二维数组
- 二维数组是特殊的一维数组,详见指针与数组章节
五、函数
-
函数的三要素:函数名、参数列表、返回值
-
函数的参数可以是:常量、变量、表达式 或者 另一个函数
-
函数变量生命周期:栈空间,被调用的时候才申请内存,调用结束立即释放
-
形式参数和实际参数,地址空间不同。值传递和地址传递
-
函数和数组:
- 在函数的形式参数中不存在数组的概念,即便传入数组定义了数组的大小也无效( 例如:int arr[10] )
- 函数中传入的数组其实是一个地址,数组的首地址,在os(操作系统)中用8个字节表示地址
- 通常在函数中调用某个数组时,传入数组的地址和长度 首地址:&arr[0]或 arr sizeof求长度len
- 函数形参是数组可以写成 *int arr[] 或者 int arr 都对
- 在C语言中,数组名是当作指针。确切的说,数组名是指向数组首元素地址的指针, 数组索引就是距数组首元素地址的偏移量。 这是为什么C语言中的数组是从0开始计数,因为这样索引可以对应到偏移量上。
-
二维数组形式参数写法:a[2][3] 或者a[][3] 不能写成a[][] 能省略行号不能省略列号
-
二维数组,若第2维大小相等,形参的第1维可以与实参的数组不同
// 在第2维大小相同时,形参的第1维可以与实参数组不同 // 例如实参数组定义为 int scores[5][10]; // 而形参数组定义为 int array[][10]; // 或者 int array[20][10]; // 两种方式都可,形参数组和实参数组都是由相同类型和大小的一维数组组成
C语言编译系统不检查第一维的大小。学习指针后了解更深入
-
关于全局变量(外部变量)要注意其作用范围,只作用在其后的函数中,在变量定义之前的函数无效
全局变量使得编程便捷,但有隐藏风险,这样所有的函数都可以操作这个变量! 少用!!
-
如果函数有多个返回值,可以定义全局变量来接收结果,也可以用指针(地址传递)实现多结果接收
-
函数调用的目的:调用者通过调用函数获得某些结果,其强调功能性的封装,既获取结果又打印结果??
六、指针
1、指针
-
指针==地址 &取地址运算符, *取地址中的值读出的运算符
- *作用一:在指针变量定义或者声明的时候 标识作用
- *作用二:取地址的运算作用
-
指针变量,存放地址的变量 int *p *起到了一个标识符作用,其他场景是运算符 指针变量也有自己的地址
-
指针是变量存放别人地址的变量,要注意类型。——6.3章节 指针变量要区分类型
// 1.类型决定了指向的空间大小 int a = 0x1234; int *p1 = &a; // *p1和*p2在取值的时候会根据指针变量的类型访问不同大小的空间 char *p2 = &a; // 2.决定指针增量的大小,增量大小和指针变量的类型有关int 增4字节,char增1字节 ++p1; ++p2;
-
使用指针的场景:
- 地址传递,函数内实现改变 函数外的变量的值,交换两个数的值
- 指针指向固定区域,开发过程中获取能够合法操作区域的地址,获得寄存机内存的地址,进行操作
-
作业:输入三个数,输出按大小排序,,,※※※※※※※※※※scanf取值符
2、数组的指针变量
-
数组的地址
int arr[3]; int *p; p = &arr[0]; p = arr; // 效果等同 p+1; // p偏移sizeof(int)大小的内存 *(p+i) == a[i]; // 等效
-
引用数组元素
- 下标法 a[i] 对数组成员变量访问时开销大,但易理解、可读性好
- 指针法 *(p+i) 指针访问正确的时候,指针的访问效率是远远大于数组名的访问效率
-
指针的偏移
*p++; // 先对p进行取值,在对p进行++ // 循环使用指针p的时候记得初始化 p = arr for(int i=0, p=arr; i<len; i++) // 循环中的初始化条件不要写多 少些花里胡哨
-
见怪不怪:
int arr[3]; int *p; p = arr; *p = *arr = arr[0]; // 都是访问第一个变量 // ※※※※※※※※以下几种写法效果等同※※※※※※※※※ p[i]; *(p+i); *(arr+i);
// ※※※※※※※指针p和数组名arr区别※※※※※※※※※: p++; // ※※※※※※※※指针变量 arr++; // ※※※※※※※※编译不过,指针常量 // ※※※※※※※关于sizeof※※※※※※※※ int arr[3]; sizeof(arr); // 结果12 表示整个数组的大小 3*4=12 个字节 数组3个元素,每个元素4个字节 sizeof(p); // 结果08 os中用8个字节表示一个地址 sizeof(int); // 结果04 int只占4字节 sizeof(int *); // 结果08 os中用8个字节表示一个地址 sizeof(char *);// 结果08 os中用8个字节表示一个地址 ※※※※※※※※只要是个指针大小就为8字节,8个字节表示其地址※※※※※※※
补充:
int arr[2][3]; sizeof(arr): 24 // 二维数组整个大小 sizeof(arr[0]): 12 // 一维数组大小,每行的大小 sizeof(arr[0][0]): 4 // 单个元素大小
-
练习题:数组翻转
3、二维数组的地址
-
设一个二维数组a[2][3]
-
C语言中规定,数组名代表数组首元素地址
而a[0] a[1] a[2]是一维数组名,因此a[0]代表了一维数组a[0]中第0列元素的地址,即为&a[0][0]
a[1] >>>> 即为 &a[1][0] a[2] >>>> 即为 &a[2][0]
-
二维数组本质还是一个数组(数组的数组),区别于一维数组的是其数组元素还是一个数组( 子数组 )
-
a是二维数组名,也为二维数组(行、父数组)地址,a[0]、*(a+0)是一维数组(列、子数组)的地址
-
表示数组首地址的方法:①数组名 ②&数组首元素
-
-
二维数组有关的指针
-
数组指针
-
指的是数组名的指针,即数组首元素地址的指针。即是指向数组的指针。
int arr[2][3]; int (*p1)[3]; p1 = arr; // p1指向了一个整型的一维数组
此时p的增量以它所指向的一维数组长度为单位
-
-
函数指针
-
函数指针也有类型要求,必须和原函数一样
-
定义与使用的时候要添加括号,如
int function1(int data); // 定义函数 int (*p2)(int data); // 定义指针 p2 = function1; // 指向函数 (*p2)(10); // 通过指针调用函数,p2必须加括号,因为*运算符的优先级比较低 int (*pcmd)(int, int); // 可以把data1和data2变量名省略,因为函数中没用到 // 形参列表强调参数的原型,参数名可省略 exit(-1); // 用到stdlib.h
使用函数指针在调用函数的时候和 调用变量一样 , 回调函数底层逻辑即为此
作用:可以根据情况,不同的命令调用不同的函数
练习题用函数指针输入1 2 3实现不同函数的调用
-
-
指针数组
-
数组中的元素都是指针类型数据,即为指针数组
-
指针数组是一个数组,每一项都是一个指针变量
int *p[4]; // []优先级比*高,p先和[]结合,p[4]是一个数组,再与前面的*结合,表示数组为指针类型,每个元素指向一个整型变量 // 区别于函数指针,这里定义指针数组的时候*p没有小括号() // *(p[i]) 取p[i]地址里的内容,加上括号,书写习惯
练习: 定义函数指针数组(数组元素为函数指针),用指针数组实现不同函数的调用
阅读: demp_hanpointArrEx.c
// 函数指针,一个指针 int (*pfunc1)(int data1, int data2); // 函数指针数组,三个指针, 名字后面加 int (*pfunc[3])(int data1, int data2); // 定义之后要初始化,不然野指针导致段错误
-
函数指针数组的调用:
int (*pfunc[3])(int a, int b)={ function1, function2, function3 }; // 定义 // 调用 int i = 1; int ret = (*pfunc[i])(a, b);
调用的时候想想怎么定义的
-
-
指针函数
-
返回值为指针的函数, 函数可以返回整型值、字符值、实型值等,也包括指针型数据,即地址
int *p; // 指针变量 int* p; // p是一个变量,是int型的指针类型,※※※※※浪着两者等同 int* func(int a, int b); // 解释:()优先级高,func先和()结合,显然为函数类型,函数前面有* 表示函数值是指针,int表示返回的指针指向整型变量
章节6.22
练习题:例8.25 a个学生b门课程 输入序号返回成绩 hanshupointreturn.c 注意利用返回的父数组指针与提取数据的指针之间的关系
作业题:例8.26
-
-
二级指针
指向 指针(地址) 的指针
int data = 100; int *p = &data; int *pp = &p; // pp是一级指针,不能通过连续两次取值符*来获得data的内容 int **p2 = &p; // p2可以访问其指向的指针指向的数据data里的内容 **p2 和 data 等同
函数里的形参之一若为 指针,实参也为指针,这就类似普通变量的值传递,不能修改主函数main里的指针的值,只是在函数中把形参指针的值修改了, 这个时候可以使用二级指针,
int function(int **ppos){---}; // 形参写二级指针,实参写一级指针取址 int main(){ int *ppos; function(&ppos); }
-
二级指针和二维数组避坑 有关联但不等同
- 二维数组array[3][4]的数组名虽然有**array的用法但是其本质不是二级指针
int scores[3][4] = {0}; int **p = scores; // 两者不等同 *p不是子数组的地址
- 正确用法
int scores[3][4] = {0}; int (*p2)[4] = scores; int **p3 = &p2; // 这个时候正确
-
总结:各种指针的应用,中小公司考题
- 复习总结 章节6.26
七、字符串
-
初识
- 通俗解释:字符串就是字符数组,双引号
char Str1[] = {'H', 'e', 'l', 'l', 'o'}; // 法1 char Str2[] = "Hello"; // 法2 char *pChar = "Hello"; // 法3 char c = 'c'; // 注意 单字符采用单引号 // 字符串初始化的时候置空 char str3[128] = {'\0'};
法2 是字符串变量,法3是字符串常量(不可修改),pChar指向了这个常量
*pChar = 'm'; // 不可对常量进行操作 Str[3] = 'm'; // 可以修改,结果Helmo
注意指针的操作:
可以保存地址,修改指向,指向字符串常量的地址空间
对野指针的内存空间不能操作,上述操作// 接上述内容 printf("%s", pChar); // %s格式声明,后面跟地址(指针)scanf函数也一样 puts(pChar); %s叫格式声明,由%和格式字符组成
-
字符串和字符数组的区别
- 字符串比字符数组的长度多**“ 1 ”, 有结束标志’ \0 '** 字符串会自动补**\0**
char str1[] = {'H','e','l','l','o','\0'}; char str2[] = "Hello"; //两者不等同
关于字符数组的赋值方法:
- 通过初始化,定义的时候直接复制
- 逐个赋值,索引访问每一个元素赋值
- strcpy或者strncpy函数
-
sizeof和strlen区别 章节7.3
sizeof用来计算数组大小,指针的大小,数据类型的大小等
strlen用来计算字符串的有效长度
-
malloc开辟空间
4个API的使用:
- 空间开辟在堆区,程序跑完之后才释放(死循环可能会耗尽计算机内存),不用于栈
- 空间使用完毕后要free,防止悬挂指针(野指针的一种)
- realloc( p, newlen ) 扩容函数,p为指向内存的指针,newlen是要增加的长度(量)
- *memset(int p, char c, int len) 形参:起始地址,填充内容’\0’ , 填充长度len
-
字符串常用操作:
char *p = "HELAJINFF"; char str[] = {'\0'}; gets(str); // 参数都为指针 puts(p);
-
字符串拷贝、拼接和比较 章节7.6
tips:逻辑判断运算符 优先级高于赋值运算符=
- 三个函数 strcpy、strcat、strcmp
- 关于比较函数注意返回值的含义 -1 1 0
-
assert断言
- 包含头文件assert.h
- 条件为假就退出
八、结构体
-
初识
- 数据类型较多,需要整合,才能描述信息
- 名字 习惯要求大写开头
- ;分号不能忘
- struct作为模板使用,不必赋初值,每一项不一定要使用
- 每个成员都是结构体中的一个域 也称域表 成员列表
- 在声明的同时也可定义变量,不推荐
-
声明、定义和使用
- 可以在声明的时候直接定义,也可在main中定义
- 访问的时候采用**. 运算符**,优先级仅次于() 和 []
-
结构体的赋值
-
通过大括号,在定义的时候初始化,一一对应填入
-
通过 . 运算符访问,分别赋值
srtuct Student stu1 = {15, 'c', "北京"}; // age、sex、addr srtuct Student stu2; stu2.age = 20; stu2.sex = 'f'; strcpy(stu2.addr, "南京"); // 赋值通常是在定义的时候或者后续用strcpy函数 // 赋值也可以是这种个别成员的赋值 struct date d2 = {.month=7, .year=2024 }; //赋值内容少于变量的个数,没给的值默认为0
-
结构体变量之间可以直接赋值
struct Student stu1 = {15, 'c', "北京"}; struct Student max; max = stu1; // 变量之间的赋值
-
结构体和数组的结合
int arr[3] = {5, 8 10}; struct Student stu[3] = {{}, {}, {}}; // 看起来类似二维数组... 括号里的括号是一个结构体
练习:选票系统
-
-
结构体指针
-
任何变量都具备四要素:类型、名称、值、地址 指针存放其地址
-
内存数据的访问:1. 变量名访问 2. 指针访问
// 内存结构体中的数据的访问: p->data; // 指针访问 stu.data; // 变量名访问
-
应用:
// 结构体数组的数组名 也是代表地址 struct Student stu[10]; struct Student *pstu; pstu = stu; // 数组名依然可以直接赋值给指针 pstu++; // 偏移量为一个数组元素的大小, 结合for循环打印结构体数组的内容
-
指针使用tips:
1 变量名访问成员变量使用.运算符, 指针访问只需要改为 指针名**->**
2 指针++,指针遍历之后会到数组的尾巴,下次遍历之前要重新指向数组组头(在选票系统使用指针实现结构体初始化就有用到)
-
-
结构体数组函数综合应用
// xm经过for循环后指向会改变,定义一个bak指针标记初始位置,或者进行加减法进行偏移 return xm-*total; // 函数指针的时候注意指向, return bak; // 标记内存空间初始位置,可以定义临时指针变量记录,也可对循环过后的指针变量进行偏移 // 多用指针的地址传递 可以改写函数为void类型
-
结构体二级指针
- 如果要通过函数调用来改变当前函数的局部变量的值,要对局部变量取地址,指针的地址传递
- 判断是否是二级指针:看保存的是否是指针变量的地址
-
联合体( 共用体 )
-
像结构体,但是又不同,区别在空间大小,联合体取决于占用的最大的元素所占用空间 ,结构体的大小是各个元素占空间之和
-
联合体注意其数据覆盖,同一时间只有一个成员有效
-
union Data{ int class; char subject[12]; }messi; // union 后面的Data可以省略
-
tips:cmd窗口部分乱码可能是因为函数返回类型没定义导致,指针指向越界
-
联合体的应用,师生系统中可以定义联合体避免空间浪费
-
-
枚举类型
-
一个变量只有几种可能的值,比如星期几,只有7种,编译器把其当常量处理
-
枚举类型其实就是整型,作用:一是让数值看起来更直观,二是将数值圈定在一个范围
-
使用方式和结构体类似,先声明在定义,最后使用
enum colors {red, yellow, green }; // 创建三个常量,red的值是0,yellow的值是1, green的值是2, 定义枚举的意义是给这些常量值名字 // 声明枚举可以指定值,只能在声明的时候指定 enum colors {red=1, yellow, green=5, NumCOLORS }; // 使用枚举主要是用作定义符号量,而不是当做枚举类型来使用 enum colors w; w = red; printf("%d", w); // 结果为w=1,NumCLORS=6,按顺序往后排
-
-
关键字typedef
-
声明一个已有的数据类型的新名字。起一个新名字
typedef int data_t; typydef struct{ data_t data; char name[32]; }stu1, *stu1;
-
九、补充
- 运算符优先权