【全篇】C语言从入门到入土

【全篇】C语言从入门到入土涵盖了变量常量 流程控制 数组 函数 指针 字符串 结构体知识点 对 C 语言有一个全方位的认识 c 语言教程

大家好,欢迎来到IT知识分享网。

【全篇】C语言从入门到入土

文章目录

第一章 前言

如何去学习,学习方法论

1.看视频学习

  • 不要拉进度条
  • 遇到熟悉的知识点或者二刷可以倍速看

2.视频中的代码

  • 理解
  • 照着打(形成关键词记忆,肌肉记忆,,,,很重要)
  • 默写打
  • 编译出错不要怕,有错误提示,,,要积累,,然后解决
  • 不要丢掉没写对的代码,一定要调试正确为止,,,
  • 错误代码也要积累,,,多多总结——(写博文,,CSDN,云笔记,空间等等)

—————刚开始low没关系,目的是能编程能做东西

第二章 初识

1.代码编译工具

配置环境变量———-目的是命令终端中的任意文件夹能识别gcc指令

安装gcc工具———–mingw—下载地址(http://www.mingw-w64.org/doku.php)

博客参考:https://blog.csdn.net/Leo_LiangXuYuan/article/details/

使用

  1. 打开命令终端cmd
  2. cd指令跳到代码文件夹
  3. 编译和运行,-o选项,指定生成的程序名字
  4. gcc test.c -o pro
    指令 c文件 生成选项 新程序名字(a.exe)
  5. gcc test.c -g 让你的程序变成可调试(不需要了解那么深了,一般在程序崩的莫名其妙,不知道哪里出了问题可以试一试)
  6. gdb a.exe 之后输入 r 进入待运行状态(之后再运行就可以看到是哪里出现了问题了)
  7. 退出的话输入 q(quit),然后y(yes确认)

    ipconfig(打开局域网配置)

2.c程序的基础框架

“最小组成”,写代码前先敲好

#include <stdio.h> //编译预处理指令 int main() //程序的入口主函数main { 
    /*你要写的代码* */ return 0; //程序退出前返回给调用者(操作系统)的值 //程序(函数,功能)结束标志 } 

3.数据的表现形式

变量
1.要先定义后使用(变量名的定义是由自己决定的,一般倾向于顾文生义)
int a = 3; int b ; b=a+1; 

请添加图片描述

一个内存空间就像一个蜂巢快递柜一样,里面的快件会变,就像内存的数据会变一样,所以叫做变量

在这里插入图片描述

2.那么如何命名变量名,以及命名规则

1.由字母数字下划线组成,且只能以下划线或者字母开头,不能以数字开头

int a int data int Mydata int _mydata int mydata int 1data 错误 

2.顾名思义,一看就可以知道是什么意思,这个要考验你的英语水平了哈哈哈哈!不会就写拼音吧,注意要区分大小写

在这里插入图片描述

3.驼峰命名法

int secondsPerYear int yiNianDuoShaoMiao SecondsPerYear second_Per_Year _myMarkData 

总之总之,比你直接int a;可强太多了

3.数据类型

在这里插入图片描述

计算机在内存中的存储方式是补码。

原码:符号位加上真值的绝对值,用最高位(第一位)来表示符号位,其余表示数值 反码:正数的反码是其本身,负数的反码是在其符号位不变的前提下,其余按位取反 补码:正数的补码是其本身,负数的补码是在反码的基础上+1 

在这里插入图片描述

3.1整型数

整数,,,int data = 10,

4个字节(一个字节8位,一共32位)c51(2) 65535 int a = ; for

3.2字符型,,ASCII码

char data3 = ‘c’,1个字节,8bit,必须是单字符‘ ‘

在这里插入图片描述

3.3浮点类型(小数)

float data2 = 3.2,,,,,,,4个字节,,,,,32bit

3.4 变量的存储类型
1.static

static变量称为静态存储类型的变量,既可以在函数体内,也可以在函数体外说明情况。(默认为0)

局部变量使用static修饰有以下特点:

  • 在内存中以固定地址存放,而不是以堆栈形式存放
  • 只要程序还没有结束,就不会随着说明他的程序段的约束而消失,他下次再调用该函数,该存储类型的变量不会重新说明,而且还保留上次调用存储。
2.extern

当变量在一个文件中的函数体外说明,所有其他文件中的函数或程序段都可引用这个变量(类似于模块化编程)

extern称为外部参照引用型,使用extern说明的变量是想引用在其他文件的中函数体外外部声明的变量。

static修饰的全部变量,其他文件无法使用。

4.强制转换

在前面加上(float)

例子:当两个数相除不能够整除时,需要进行强制转换,来得到后面的小数

/*无强制转换/ #include<stdio.h> int main() { 
    int a =10; int b =3; float c; c =a/b; printf("%f",c); return 0; } /做强制转换*/ #include<stdio.h> int main() { 
    int a =10; int b =3; float c; c =(float)a/b; printf("%f",c); return 0; } 

如果不做强制转换
会自动把小数点后面的省略

在这里插入图片描述

做完强制转换之后,完美!!!

在这里插入图片描述

常量
1.整型常量

常量是指在程序运行期间其数值不发生变化的数据。整型常量通常简称为整数。

整数可以是十进制,也可以是八进制,十六进制。例如:

十进制:15 八进制:21 
  • 在程序运行过程中,其值不能改变
  • 符号常量 #define PI 3.14 (宏定义)

转义字符

在这里插入图片描述

介绍几个概念

C 语言自加 ++ / 自减 — 运算符实际就是对变量本身做 +1 或者 -1 操作

1.自加自减运算符

(自加自减运算符均为单目运算)

1.只需要一个运算量。若运算符位于运算对象前面时,称为前缀运算,如++a和 – -a;

2.而运算符位于运算对象后面时,称为后缀运算符,如a++和a- -,自加和自减运算符的功能是将运算对象加1或减1后,再将结果保存到运算对象中。

前缀和后缀运算分别等价的运算如下:

a-- //每一次减1 a++ //每次自加1 
#include <stdio.h> int main(){ 
    int a =5; int b =6; printf("a=%d,b=%d\n",a,b); a++; b--; printf("a=%d,b=%d\n",a,b); ++a; --b; printf("a=%d,b=%d\n",a,b); a=a+5; b=b-3; printf("a=%d,b=%d\n",a,b); return 0; } 
2.三目运算符

z = x>y?x:y 这句话的意思是,,x是否大于y,打个问号,如果是的话等于x,不是的话等y

4.输入输出

4.1printf–打印

输出表列中,,可以是数据类型,可以是一个表达式。

在这里插入图片描述

格式声明

1.原样输出,,printf(“hello,world”);

2.%占位符/格式字符——printf(“a=%d”,a);

在这里插入图片描述

d 十进制整数
c 单个字符,输出一个字母
s 多个字符
x 以16进制格式输出
p 一般打印内存地址,也是16进制格式输出,输出地址,取变量地址的运算符号&

f————-重要的一个;

在这里插入图片描述

指定位数

在这里插入图片描述

在这里插入图片描述

%-m.nf

指定位数

在这里插入图片描述

以下了解即可

在这里插入图片描述

在这里插入图片描述

4.2 scanf /扫描键盘

需要注意的地方

1.地址符号&,,不要忘记

在这里插入图片描述

也可以分开,3个变量,就3个scanf

2.原样输入

在这里插入图片描述

scanf格式中有什么字符,输入的时候也要输入!!!!!!!!!!比较坑爹的地方就是这里,设想一下如果我们不是写这段代码的人我们又怎么知道,需要原样的输入是什么呢,解决办法就是去掉。。直接%f%f%f

3.注意字符

在这里插入图片描述

4.混合输入,,,,,,,,,主要还是了解输入控制流程

在这里插入图片描述

!!!!!!!!!!!!!!涨知识的时候

在这里插入图片描述

在这里插入图片描述

5.其他

getchar();吸收空格符

putchar();

puts();

gets();//会涉及数组,,,后面再说

sgkbc1

后面的事后面再聊,,,恭喜你已经对C语言有了初步的认识,开启对下一章流程控制的认知

一个人如果总是太过于在乎他人的评价,就会失去自己。人生最怕的事之一就是把别人的眼光当成自己生活的唯一标准。到最后,既没有活成自己喜欢的样子,也没有活成自己想要的样子。

学会取悦自己,丰富自己,在努力的路上,美好才会回过头来拥抱你。与其总是感觉时间不够用,不如和时间做朋友,尽全力过好每一天。多点圆满,少些遗憾。

6.结语:编程案例——了解为主

在这里插入图片描述

不要看案例程序,,独立自主编写哈哈哈哈哈哈

在这里插入图片描述

如果不加getchar();吸收一下回车符的话,输入完一个字母之后直接跳完程序,,,,以下为改进

在这里插入图片描述

输入两个数,获得两个数加减乘除的值

在这里插入图片描述

编译一个密码

printf函数会了,putchar不会用…………

在这里插入图片描述
在这里插入图片描述


第三章 流程控制

正式开始对流程控制语句的学习

不是你的能力,决定了你的命运,而是你的决定,改变了你的命运。

想,都是问题,做,才是答案。站着不动,永远是观众,想到做到,才是王道

控制类语句

在这里插入图片描述

在这里插入图片描述

帮助理解

在这里插入图片描述

1.if()…else… 条件语句,层层递进的

if(条件){ 
     表达式01 }else{ 
     表达式02 }; 

在这里插入图片描述

在这里插入图片描述

关系运算符

在这里插入图片描述

如何交换两个数的值?

在这里插入图片描述

不交换土办法

在这里插入图片描述

逻辑运算符

在这里插入图片描述

if…else嵌套
include <stdio.h> int main(){ 
     if(){ 
     }else if(){ 
     }else if(){ 
     } return 0; } 
如果有三个数,如何让它从小到大排序,要用到冒泡排序法,之后学习
#include <stdio.h> int main(){ 
     int a,b,c; printf("请依次输入三个数\n"); scanf("%d%d%d",a,b,c); //分析出会出现三种情况,a最大,,b最大,,c最大 if(a>b&&a>c){ 
     } return 0; } 

2.switch( ) case… 并列,多分支语句

可以是字符,也可以是数字,,,直接看代码学习怎么用

在这里插入图片描述

switch(输入的条件){ 
     case 1: 表达式01; break; //必须要加上这个语句才能结束 case 2: 表达式02; break; case 3; 表达式03; break; default: 条件都不符合;//在不满足上述所有情况时使用 } 

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.1练习题

在这里插入图片描述

#include<stdio.h> int main(){ 
     int x,y; printf("请输入x的值为多少"); scanf("%d",&x); switch(x){ 
     case 0: y=x; break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: y=2*x-1; break; default: 3*x-11; break; } printf("你输入的数y=%d,x=%d",y,x); return 0; } 

运行结果,,基本上算是成功了吧,,,无法控制是负数的情况,还有小数,比较坑,,这个故事告诉我们,要选择正确的语句,,如果if…else会简单很多,,不要勉强自己在这里插入图片描述

3.while循环控制语句

在这里插入图片描述

while(条件){ 
     //注意条件只识别,0和1,如果一直是一个正数,这就是一个死循环,要杜绝这种情况,会把单片机内存无限占用 //Ctrl+c可以强行终止 表达式; } /举个例子,,输入十次我爱你*/ #include <stdio.h> int main(){ 
     int a=0; while(a<10){ 
     a=a+1; //每循环一次,a都会加上一个1 //还有一种写法,比较简介 a++; } return 0; } 
3.1练习题

在这里插入图片描述

/*错误案例*/ #include<stdio.h> int main(){ 
     int a=0; int b=0; while(a<=100){ 
     a=a+1; b=b+a; printf("b的值为%d",b); } printf("最终值为%d",b); return 0; } 

在这里插入图片描述

发现错误了嘛??先写条件的话,会多算一个101,,,以后要注意了

4.do…while

先做一次循环再判断,

在这里插入图片描述

do{ 
     }while(); 

5.for(){ }

#include<stdio.h> int main()//三个表达式 { 
     int sum; int data=1;//表达式1,,条件的初始值 while(data<=100){ 
     //表达式2,,条件的临界值 sum=sum+data; data++; //表达式3,,条件的改变 } printf("%d\n",sum); return 0; } 

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

语句全省略的话就是一个死循环

6.break,,,在还没有达到临界值的情况下,提前结束循环

在这里插入图片描述

在这里插入图片描述

介绍一个重要概念,,取余

% //取余

在这里插入图片描述


break结束整个循环,,,,contine仅仅结束本次循环

7.用contine提前结束本次循环

在这里插入图片描述

#include<stdio.h> int main(){ 
      for(int a=100;a<=200;a++){ 
      //注意要用分号,,因为每个表达式换成单个 /*首先要明确一点,能被3整除,则余数就是0*/ if(a%3 ==0){ 
      continue; } printf("%d",a); } return 0; } 

运行结果

在这里插入图片描述

循环嵌套

#include <stdio.h> int main(){ 
      int i,j; int data =1; for(i=1;i<=5;i++){ 
      for(j=1;j<=3;j++){ 
      printf("i=%d , j=%d\n",i,j); //为了打印出行列,,研究行列的关系 printf("data=%d",data++); //为了研究一共有几个数 } } } 

运行结果

数的个数等于,行列相乘,,

在这里插入图片描述

嵌套练习题

在这里插入图片描述

#include<stdio.h> int main(){ 
      int i,j; for(i=1;i<=4;i++){ 
      for(j=1;j<=5;j++){ 
      printf("%d ",i*j); } printf("\n"); } return 0; } 

运行结果

在这里插入图片描述


第四章 数组

———————-数组的引入

你所有的压力,都是因为你太想要了,你所有的痛苦,都是因为你太较真了。有些事不能尽你意,就是在提醒你改转弯了。

如果事事都如意,那就不叫生活了,珍惜所有不期而遇,看淡所有的不辞而别。

在这里插入图片描述

4.1如何定义一个数组

1.相同的数据类型,,,关于中括号[ ]的解释
  • 定义的时候表示数组中元素的总个数,int a[10];
  • 下标法表示数组中的某个元素,从0开始计数

在这里插入图片描述

2.数组如何[遍历]

遍历

  • 下标法访问
  • 结合循环控制语句
  • 数组的地址为连续的
/ *第一步;先给数组赋值 第二步;把数组的值给打印出来 *过程中用到的是for循环/ #include<stdio.h> int main(){ 
       int a[10]; int b; /*1.给数组赋值*/ for(b=0;b<=9;b++)//一个数组里面有十个,从0开始,所以到9一共是10个,,刚好 { 
       a[b]=100+b; } puts("赋值完成"); for(b=0;b<=9;b++){ 
       puts("a[b]=%d",a[b]);这个有坑,,puts只能输出纯字符 printf("a[b]=%d\n",a[b]);//这个也有坑,b是输出不出来 printf("a[%d]=%d\n",b,a[b]);//这样可以输出,可以看下面运行结果 } puts("done"); return 0; } 

运行结果,,,好好看好好学

在这里插入图片描述

3.初始化的方式

1.全部赋值,,,数很多的时候会很麻烦

在这里插入图片描述

2.部分赋值,,优先向前排,其余剩下的赋值为0

在这里插入图片描述

3.初始化为0

在这里插入图片描述

4.写法,由数组元素的个数来确定数组的长度

在这里插入图片描述

4.数组的各种初始化方式以及數組的大小计算(代码)

sizeof() 关键字,,可以计算括号里面数据的内存空间大小!!!!!注意他不是函数,面试可能会问

#include<stdio.h> int main() { 
       int array1[5]={ 
      1,2,3,4,5}; //1.全部赋值 int array2[5]={ 
      1,2,}; //2.部分赋值 int array3[5]={ 
      0}; //3.全部赋值为0 int log; log =sizeof(array1)/sizeof(array1[5]); //数组的个数,或者长度= 数组的总大小/一个数组的大小 printf("array:%d\n",log); for(int a=0;a<5;a++){ 
       printf("地址%p,,数字%d\n",&array1[a],array1[a]); } return 0; } 

在这里插入图片描述

4.2编程案例

1.数组初始化及逆序输出

在这里插入图片描述

#include <stdio.h> int main() { 
       int array[10]; for(int a =0;a<=9;a++){ 
       //初始化数组 array[a] =a; } for(int a =0;a<=9;a++){ 
      //前面定义的变量到后面就不能用了 printf("正常顺序为%d ",array[a]); } printf("\n"); for(int c =9;c>=0;c--){ 
       printf("逆序输出为%d ",array[c]); } return 0; } 

在这里插入图片描述

2.斐波那契数列

在这里插入图片描述

#include<stdio.h> int main(){ 
       int array[30]; int array[0]=0; //赋值不需要定义 int array[1]=1; for(int a=3;a<=30;a++){ 
       //最好做一个数组长度的计算 array[a]=array[a-1]+array[a-2];//应该是2,,其实2已经是第三个数了 } for(int a=0;a<=30;a++){ 
       printf("%d ",array[a]); } return 0; } 

报错了我滴宝,,,!!注意了

在这里插入图片描述

再来一次

#include<stdio.h> int main(){ 
       int array[30]; array[0]=0;//不需要加数据类型 array[1]=1; //int arraysize =sizeof(array[])/sizeof(array[0]);//错误写法 int arraysize =sizeof(array)/sizeof(array[0]); for(int a=2;a<=arraysize;a++){ 
       array[a]=array[a-1]+array[a-2]; } for(int a=0;a<=30;a++){ 
       printf("%d ",array[a]); } return 0; } 

在这里插入图片描述

3.冒泡排序法,面试要考滴

现在来分析一下,,有这么四个数,12,8,13,9————-从小到大排列

这个数组的长度为4,数组从0开始,所以正好到3

1.首先我们来进行第一轮第一次比较,把 i 设为行(轮数),,j 设为列(比较的次数),,

12跟8进行比较,得到的结果为,,,12跟后面的13比较,得到的结果为,,,13跟9开始比较,得到一个结果,,此刻,最大的一个数冒出水面

2.开始第二轮第一次比较,此时只剩下3个数 8,12,9,,,8跟12开始比较,得出8,12,9,,,然后第二次比较12跟9,得出8,9,12

最大的一个数,12冒出水面

3.开始第三轮第一次比较,此刻为8,9两个数,比较得出,8,9,,9是最大数

i j 0 1 2 3
0 8,12,13,9 8,12,13,9 8,12,9,13 13
1 8,12,9 8,9,12 12
2 8,9 9
3

分析上述

通过以此,我们不难看出,原理其实就是,第一个数跟第二个比较,如果满足条件就不需要交换,如果不满足就需要交换,然后第二个数跟第三个比较,第三个跟第四个比较,以此类推

那么,我们分析他的轮数,一共有四个数,比较了三轮,四(数字的个数)-1 = 三轮;;;现在看次数,第一轮比较了3次;第二轮2次;第三次1次,次数依次在递减,这个次数跟轮数有什么关系呢??

3=4(数组长度)-1(猜出来的数) 2=4 – 2(多出来一个1哪里来的?) 1=4-3(又多出来一个2?)什么规律??????????轮数为i < len-1,,,那次数呢?,,j < len – 1-x,这个x与轮数有什么关系,,,j < len – 1 -i;

/*从小到大排序*/ #include <stdio.h> int main(){ 
       int array[]={ 
      12,8,13,9}; int tmp; int len =sizeof(array)/sizeof(array[0]); for(int i=0;i<len-1;i++) { 
       for(int j =0;j<len-1-i;j++) { 
       if(array[j]>array[j+1]) { 
       tmp = array[j+1]; array[j+1] = array[j]; array[j]=tmp; } } } /*给个反馈*/ for(int a =0;a<len;a++) { 
       printf("%d ",array[a]); } return 0; } 
4.简单排序法,面试题

怎么比呢?永远都是第一个数依次跟第234…后面的数相比较,,如果满足就换,不满足就不动,反正永远是第一个跟后面的比较,然后所有的都比较完之后,开始第二个数依次跟后面的比较,,,,以此类推,最后排完整个

还是四个数,8 12 13 9,,从大到小排序

i j 0 1 2 3
0 12 8 13 9 12 8 9 9 8 13
1 13 8 12 9 12 8 9 12
2 13 8 12 9 9
3

我们由现象去分析本质,,去推导出一个可以适用于所有数排序的规律

二维数组

1.什么时候要用二维数组

在这里插入图片描述

在这里插入图片描述

2.怎么样定义一个二维数组

在这里插入图片描述

在这里插入图片描述

3.二维数组的初始化
3.1.按行列的初始化

在这里插入图片描述

3.2.没明确行列,类似一维数组

在这里插入图片描述

3.3.部分赋初值

1.在这里插入图片描述

2.在这里插入图片描述

3.在这里插入图片描述

4.可以不写行,但是一定要写列

在这里插入图片描述

4.二维数组的遍历

在这里插入图片描述


第五章 函数

首先恭喜你已经学到了函数这一部分,,革命尚未成功,同志仍需努力。我真心的希望你能戒骄戒躁,稳扎稳打,去突破如今的桎梏,找到自己一生所热爱的事物,加油加油橘猫

1.为什么要用函数

  • 避免代码冗长
  • 模块化的设计思路
  • 按功能划分,每个函数代表一个功能,而函数的名字要体现函数的功能含义,类似变量标识符
    y=f(x)
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.函数要先定义再使用,和变量一个道理

在这里插入图片描述

定义:抓住三要素!牢牢记住!!!!!!!!!
  • 函数名————-体现功能
  • 参数列表——-比如y=f(x),,,x就是参数 比如z=f(x,y),,x,y就是参数(参数的个数根据需求自行定义)
  • 返回值——还比如y=f(x),,,y是函数根据x的值和f的功能执行后的结果

函数体————执行什么样的功能,涉及的处理代码叫做函数体

3.函数的定义与调用

1.定义无参数的函数

在这里插入图片描述

#include <stdio.h> void printfwelcome(){ 
        printf("====================\n"); printf("欢迎来到我的程序\n"); printf("====================\n"); } //1.函数调用时,要注意是怎么调用的,,函数名要一致,数据类型不要写(定义的时候才用) int main(){ 
        printfwelcome();//在运行到这个函数的时候会跳回上面定义的函数 return 0; } 
2.定义有参数有返回值的函数
  • 如y=f(x),,一个返回值,一个参数
    #include <stdio.h> /一个参数,带着一个返回值,这个返回值是个整型,所以用int*/ int getDataFormX(int x) //形式参数,需要包含变量名(),变量类型 { 
              //x变量名是可以随意随意起的 int data; data = x-1; return data; /*还有一种写法,直接就是一个返回值*/ // return x-1; } /*在这整个过程中我们关心的只有值和数据的传递*/ int main(){ 
              int x; int y; puts("请输入一个数:"); scanf("%d",&x); //首先你输入的x的值 y = getDataFormX(x);//然后这个里面的x的值,会传给上面定义的函数里面的变量,,他们是值的传递,地址空间不一样 //在定义的函数里面一顿操作之后,会返回一个数,这个数在传回来给到现在的 y;  printf("x =%d,y =%d",x,y); return 0; } 
  • 一个返回值,两个参数
#include <stdio.h> /*自己定义的函数*/ int add(int a,int b)//两个数据,,2参数, { 
        int c; c = a+b; return c; } /*主函数/ int main(){ 
        int x,y,z; puts("请输入一个数:"); scanf("%d",&x); puts("请再输入一个数:"); scanf("%d",&y); //x,y两个数据值传给上面定义的函数,,在定义函数里面定义两个数据类型来承接过来的数据,,返回一个值回来,给到z z =add(x,y);//--------要注意x,y是用来传递数值的变量,,上面已经定义过,所以不要加数据类型,易错点  printf("%d+%d=%d",x,y,z); return 0; } 
  • 一个返回值,三个参数,多个参数可以以此类推
#include <stdio.h> /*自己定义的函数*/ //三要素:返回值,参数列表,功能 int add(int a,int b,int z) //函数原型 { 
        int c; c = a+b+z; return c; } /*主函数/ int main(){ 
        int x,y,z,ret; puts("请输入一个数:"); scanf("%d",x); puts("请再输入一个数:"); scanf("%d",y); puts("请再输入一个数:"); scanf("%d",z); ret =add(x,y,z); printf("%d+%d+%d=%d",x,y,z,ret); return 0; } 
3.定义空函数

程序设计,模块设计的时候,占坑

在这里插入图片描述

就是先捋清思路流程,,然后再开始向里面写代码,,不至于直接报错

4.函数调用

新手经常犯的错误

  • int add(2,3) 带了返回值类型
  • add(int a, int b) 形参带类型了

一些见怪不怪的操作

函数可以当做表达式

在这里插入图片描述

函数调用当做其他函数调用的参数

在这里插入图片描述

4.形式参数和实际参数

在这里插入图片描述

传递参数,传递的是值,,形参和实参值相同,但是地址空间不同

#include <stdio.h> //数据和值 int test(int x) //形式参数,需要包含变量类型,变量名(),,,, /*生命周期:栈空间 *被调用的时候才为形式参数申请内存,调用结束,内存有被系统释放 *局部变量的有效作用空间(作用域)要记得*/ { 
        int y=5; printf("test的x内存地址是%p,数值是%d\n",&x,x); return (x-y); } //变量的四个要素:名 类型 值 地址 int main() { 
        int x,y; puts("请输入一个数:"); scanf("%d",&x); printf("main的内存地址是%p,数值是%d\n",&x,x); y = test(x);//实际参数,,会把这个函数的返回值给到y printf("x=%d,y=%d",x,y); return 0; } /我在单片机看见有一种操作 void delay(unsigned int j) { while(j--); } void main() { delay(1000); 他们用的是此方法来进行延迟,延迟函数的数值可改,所以延迟时间也可以改,是不是很具有参考意义?? } */ 

在这里插入图片描述

1.全局变量与局部变量
编程案例
1.输入两个整数,要求输出最大值,用函数实现
#include <stdio.h> int get_bigger_fordata(int x,int y) { 
        /*方法1 * int z; * if(x>y){ * z = x; * }else{ * z = y; * } * return z;*/ /*方法2 * int z; * z =x>y?x:y * return z;*/ /*方法3 return x>y?x:y;*/ } int main() { 
        int x,y,bigone; //提示输入两个数 puts("输入两个数\n"); //输入两个数 scanf("%d%d",&x,&y); //调用函数比较大小 bigone = get_bigger_fordata(x,y); //输出输入的两个数,比较出那个数最大 printf("你输入的两个数分别是%d,%d, 最大的那一个是%d\n",x,y,bigone); return 0; } 

这里我犯了一个大毛病就是在用 scanf 函数时需要取地址&,没有用的话,编译会出现这种情况

其次注意puts后面的\n,直接跳过了一格,puts是自动换行的

在这里插入图片描述

如果是小数或者字符呢??自行体会

在这里插入图片描述

调用过程
  1. 内存空间在这里插入图片描述
  2. 值传递在这里插入图片描述
  3. 值返回

    (如果函数返回类型是void,函数体可以不用加return,返回值要注意类型,如果类型不同,可能会发生强制转换影响结果或编译警告)在这里插入图片描述

  4. 内存释放(如果想要发生值改变,后面可以学到指针之后可以用指针传递地址)

    在这里插入图片描述

函数调用的条件
  1. 函数已被定义
  2. 调用库函数在这里插入图片描述
  3. 函数的声明在这里插入图片描述

在这里插入图片描述

5.函数的嵌套

一步步调用,一步步返回

在这里插入图片描述

练习
用函数嵌套来实现四个数里取得最大值
#include <stdio.h> int getfortwo(int a,int b) { 
        return a>b?a:b; } int get_four_fordata(int a,int b,int c,int d) { 
        int max; max = getfortwo(a,b); max = getfortwo(max,c); max = getfortwo(max,d); return max; } int main() { 
        int a,b,c,d,best; puts("请输入四个数"); scanf("%d%d%d%d",&a,&b,&c,&d); getchar(); best = get_four_fordata(a,b,c,d); printf("你输入的四个数分别是%d,%d,%d,%d,最大的是%d\n",a,b,c,d,best); return 0; } 

在这里插入图片描述

6.函数的递归(嵌套了自己)

一般在实际中很少用

编程案例

在这里插入图片描述

解题思路

在这里插入图片描述

#include <stdio.h> int get(int a) { 
        int age; //那么通过if语句,如果你想得到的不是1,那么他会一直在调自己,直到=1之后,输出age的值 //假如a=2;age = get(1)+2;,,然后这个get(1)就会调自己,get(1)=10,这样就可以得出结果了 if(a == 1) { 
        age = 10; }else{ 
        age = get(a-1)+2;//这里我就有一个问题,什么时候才可以让它停下?? } return age; } int main() { 
        int age,num; printf("请输入你想知道第几个学生的年龄\n"); scanf("%d",&num); age = get(num); printf("第%d的年龄为%d",num,age); return 0; } 

一开始写成了if(a=1),他就变成了一个定值,就会出现下面的这种情况

在这里插入图片描述

求阶乘

在这里插入图片描述

//我直接改上面的代码哈哈哈哈,,需要注意的就是数过大就会越界 #include <stdio.h> int get(int num) { 
        int b; //那么通过if语句,如果你想得到的不是1,那么他会一直在调自己,直到=1之后,输出age的值 //假如a=2;age = get(1)+2;,,然后这个get(1)就会调自己,get(1)=10,这样就可以得出结果了 if(num == 1) { 
        b = 1; }else{ 
        b = get(num-1)*num;//这里我就有一个问题,什么时候才可以让它停下?? } return b; } int main() { 
        int b,num; printf("请输入你想知道几的阶乘\n"); scanf("%d",&num); b = get(num); printf("%d的阶乘为%d",num,b); return 0; } 

在这里插入图片描述

7.数组作为函数中的参数

传递数组中的某个元素(意义不大)
//我要传递过来单个数组怎么弄? #include <stdio.h> void printfdata(int data)//注意这里是一个普通的整型数,,而不是数组 { 
        printf("%d\n",data); } int main() { 
        int array[] = { 
       5,6,4,2,9,7};//这个拓展一下,就是为了看看,不定义数组大小会不会报错,结果是不会 for(int a = 0;a<6;a++) { 
        if(a == 5) { 
        printf("\n"); } printf("%d ",array[a]); } int arr[3] = { 
       1,2,3}; printfdata(arr[2]);//我们把第三个数给传过去 return 0; } 

在这里插入图片描述

数组名当做函数实际参数
//我要是传递整个数组呢? #include <stdio.h> void printf_arr(int arr[3])//把这三个数组定义出来 { 
        for(int a=0;a<3;a++){ 
        printf("%d\n",arr[a]); } } int main() { 
        int arr[3] = { 
       1,2,3}; printf_arr(arr);//这样会直接把整个数组给传递过去 return 0; } 

在这里插入图片描述

关于数组作为函数参数的一些坑
//还是用的上面的代码,我们算一下数组的分别在实参和形参的大小 #include <stdio.h> void printf_arr(int arr[3])//把这三个数组定义出来 { 
        for(int a=0;a<3;a++){ 
        printf("%d\n",arr[a]); } printf("arr里面的array%d\n",sizeof(arr));//注意计算大小时是arr数组名,而不是数组元素 } int main() { 
        int arr[3] = { 
       1,2,3}; printf_arr(arr);//这样会直接把整个数组给传递过去 printf("main里面的array%d\n",sizeof(arr));//注意sizeof是关键字而不是函数 return 0; } 

在这里插入图片描述

//如果数组的个数变成10个呢? #include <stdio.h> void printf_arr(int arr[10])//把这三个数组定义出来 { 
        for(int a=0;a<10;a++){ 
        printf("%d\n",arr[a]); } printf("arr里面的array%d\n",sizeof(arr));//注意计算大小时是arr数组名,而不是数组元素 } int main() { 
        int arr[10] = { 
       1,2,3,4,5,6,7,8,9,10}; printf_arr(arr);//这样会直接把整个数组给传递过去 printf("main里面的array%d\n",sizeof(arr));//注意sizeof是关键字而不是函数 return 0; } 

在这里插入图片描述

我们看到,实参变成了40个字节,一个int整型数大小为4个字节,,但是形参依旧是固定的8个字节

//下面我们开始分析一下 //至于上面会出现一个警告我们不需要管,还没有学到指针 #include <stdio.h> void printf_arr(int arr[10],int len)//形参中不存在数组的概念,即便中括号约定了数组的大小,也是无效的 //void printf_arr(int arr[],int len),,,不写数组元素的个数也是对的 { 
        //传递过来的是一个地址,数组的首地址 for(int a=0;a<len;a++){ 
        printf("%d\n",arr[a]); } printf("arr里面的array%d\n",sizeof(arr));//在OS操作系统中,用8个字节来表示地址 } int main() { 
        //里面还存在一个问题是,我们改变数组长度之后,函数封装里面的数组长度也需要更改,正常的我们一般不喜欢去更改函数里面的逻辑 //那么我们能不能直接计算出数组的长度呢? int len; //优化部分 int arr[10] = { 
       1,2,3,4,5,6,7,8,9,10}; len = sizeof(arr)/sizeof(arr[0]);//一个好方法 printf_arr(arr,len);//这里需要注意把len的值也给传递过去 //1.数组名代表整个数组的首地址 //2.那么既然上面说到数组传递的是一个首地址,那么我们也可以这样写, printf_arr(&arr[0]);把数组第一个元素的地址传过去 printf("main里面的array%d\n",sizeof(arr)); return 0; } 
有意思的案例,关于地址(未来的指针)

当发生函数调用的时候,会开辟出一个地址给这个函数,此时这个数的改变只会在新开辟的地址里面改变,原先main函数里面的数值是不会变的

在这里插入图片描述

而当他传递数组时,却能改变数值,是因为他传递过去的是一个数组的首地址。。。

在这里插入图片描述

编程案例
算出两个班级学生成绩的平均分(API)
#include <stdio.h> void initarray(int array[],int len) { 
        for(int a=0;a<len;a++) { 
        printf("请输入第%d个学生的分数\n",a+1); scanf("%d",&array[a]); } } void printfarray(int array[],int len) { 
        printf("总共有%d个学生\n",len); for(int a=0;a<len;a++) { 
        printf("他们的成绩依次为%d\n",array[a]); } } float average(int array[],int len) { 
        int sum = 0; float aver = 0.0; for(int a=0;a<len;a++) { 
        sum+=array[a]; } aver = (float)sum/len; return aver; } int main() { 
        int classone[5]; int classtwo[10]; float averclassone; float averclasstwo; int lenclassone = sizeof(classone)/sizeof(classone[0]); int lenclassatwo = sizeof(classtwo)/sizeof(classtwo[0]); /*这是我一开始写的函数,垃圾的要死哈哈哈,下面来优化一下 initclassone(); initclasstwo(); printfclassone(); printfclasstwo(); averclassone(); averclasstwo();*/ //首先我们来解释一下什么叫API,它预先把复杂的操作写在一个函数里面,编译成一个组件(一般是动态链接库)程序员只需要简单的调用 //这些函数就可以用完成复杂的工作。 //这些封装好的函数就叫做API。更加通俗讲:别人写好的代码,或者编译好的程序,提供给你使用,就叫作api。 initarray(classone,lenclassone); initarray(classtwo,lenclassatwo); printfarray(classone,lenclassone); printfarray(classtwo,lenclassatwo); averclassone = average(classone,lenclassone); averclasstwo = average(classtwo,lenclassatwo); printf("第一个班的平均分为%f\n",averclassone);//一开始这里用%d来承接了,结果直接算不出来结果 printf("第二个班的平均分为%f\n",averclasstwo); return 0; } 

在这里插入图片描述

在这里插入图片描述

8.二维数组作为函数的参数,他的形参怎么写?

正确写法

int arr[ 5 ] [ 2 ], int arr [ ] [ 5];

错误写法

int [ ] [ ]

在这里插入图片描述

/*回顾一下之前的二维数组*/ #include <stdio.h> int main() { 
        int i,j; int arr[2][3] = { 
       { 
       4,5,6},{ 
       7,8,9}}; for(i=0;i<2;i++) { 
        for(j=0;j<3;j++) { 
        printf("%d",arr[i][j]); } putchar("\n"); } return 0; } 
/*封装成函数的形式*/ #include <stdio.h> void printf_arr_double(int arr[][])//3.所以这里就出错了 { 
        int i,j; for(i=0;i<2;i++) { 
        for(j=0;j<3;j++) { 
        printf("%d",arr[i][j]); } putchar("\n"); } } int main() { 
        //1.我们要关心的是每一维数组里面包含的元素数 int arr[2][3] = { 
       { 
       4,5,6},{ 
       7,8,9}};//2.特殊的一维数组,每个元素又是一个数组,大小确定 printf_arr_double(arr); return 0; } 

然后你就会发现会爆错误

在这里插入图片描述

关心两点
  • 数组数据类型
  • 二维中的一维数组有多少个

在这里插入图片描述

练习

练习:有3×4矩阵,初始化它并输出,然后求最大值并输出

#include <stdio.h> void init_arry(int arr[][4],int hang,int lie) { 
        int i,j; for(i=0;i<hang;i++) { 
        for(j=0;j<lie;j++) { 
        printf("请输入第%d行,第%d列的数\n",i+1,j+1); scanf("%d",&arr[i][j]); } } } void printf_arr_double(int arr[][4],int hang,int lie) { 
        int i,j; for(i=0;i<hang;i++) { 
        for(j=0;j<lie;j++) { 
        printf("%d ",arr[i][j]); } putchar('\n');//注意这个要用单引号啊啊啊啊啊啊!!! } } int get_max_arr_double(int arr[][4],int hang,int lie) { 
        int i,j; int max =arr[0][0]; for(i=0;i<hang;i++) { 
        for(j=0;j<lie;j++) { 
        if(max < arr[i][j]) { 
        max = arr[i][j]; printf("最大的数在第%d行,第%d列",i+1,j+1); } } } return max; } int main() { 
        /*1.把数组几行几列给输入进去 *2.把数组遍历打印出来 *3.获取出最大值*/ int x,a; int arr[3][4]; init_arry(arr,3,4); printf_arr_double(arr,3,4); a = get_max_arr_double(arr,3,4); printf("最大的数为%d",a); return 0; } 

9.全局变量

在这里插入图片描述
在这里插入图片描述

编程案例

班上10 个学生,封装一个函数,调用该函数后获得班上的平均分,最高分,最低分

#include <stdio.h> //我们没有办法返回多个值,所以只能定义一个全局变量来承接这个数 int min,max; float aver; float quanjv(int score[],int len) { 
        int sum = 0; min = max = score[0]; for(int i = 0;i<len;i++){ 
        if(min>score[i]){ 
        min = score[i]; } if(max <score[i]){ 
        max = score[i]; } sum +=score[i]; } return (float)sum/len; } int main() { 
        //1.做出一个数组 //2.调用函数,返回平均值 int score[10] = { 
       12,54,15,2,65,88,41,34,51,62}; float len = sizeof(score)/sizeof(score[0]); aver = quanjv(score,len); printf("最大的数是%d,最小的数是%d,平均分是%f",max,min,aver); return 0;//返回值只能返回一项,以后学了结构体之后可以返回多个值 } 

练习题

1.要求输入10个数,找出最大数以及最大数的下标

#include <stdio.h> int j; void init_total(int arr[],int len) { 
        printf("请依次输入10个数\n"); for(int a = 0;a<len;a++) { 
        scanf("%d",&arr[a]); } } int find_max(int arr[],int len) { 
        int max = arr[0]; for(int i = 0;i<len;i++) { 
        if(max < arr[i]) { 
        max = arr[i]; if(max == arr[i]) { 
        j = i; } } } return max; } int main() { 
        int arr[10]; int maxx; int len = sizeof(arr)/sizeof(arr[0]); //1.提示输入 init_total(arr,len); //2.存放起来 //3.找最大数以及下标 maxx = find_max(arr,len); printf("最大值为%d,他是第%d个元素\n",maxx,1+j); return 0; } 

第六章 指针

都说指针是C语言里面最难的,今天我倒要看看到底有多难,哈哈哈哈,很恭喜你闯到了这一关,至于最后的结果如何,咱们拭目以待

1.认识一下指针

1.指针 == 地址;访问变量的两种方式:1.变量名 ,2.地址

int a = 10; 类型 变量名 内存地址 值 //这样我们就引出来了,指针 1.变量名能访问 2.通过地址也能访问 & 取地址运算符 * 将地址内的值读出运算符 /*/ #include <stdio.h> int main() { 
        int a = 10; printf("a的数值为%d\n",a); printf("a的地址为%p\n",&a); printf("a的数值为%d\n",*(&a));// * 是取值运算符,它可以把地址里面的数据来取出来 //这个就是另外一种通过地址来取出来数值的方式 return 0; } 

这个图怎么看??? 1.首先这个 3 是被一个变量 i 给存着,他的地址是 2000 2.而下面有一个存放着地址 2000(变量i的地址)的变量i_pointer,而这个变量本身又有一个自身的地址 3020

在这里插入图片描述

2.指针变量 = 存放地址的变量

2.1如何定义一个指针变量以及如何使用指针变量
  • ​ * 的运算作用
#include <stdio.h> int main() { 
        //什么是整形变量,存放整型数的变量 //什么是字符变量,存放字符型的变量 //什么是指针变量,存放指针的变量 //什么是指针变量,存放地址的变量 int a = 10; int *p; //这里的*是一个标识符,告诉系统我是一个指针变量,是用来保存别人地址的,和下方的运算符不同 p = &a; //int *p = &a; printf("变量名访问a:%d\n",a); printf("地址访问a:%d\n",*(&a));//取值运算符,把a地址存放的数值来取出来 printf("a的地址是a:0x%p\n",&a); printf("变量名访问a:%d\n",a); retrun 0; } 
2…2变量的访问方式

在这里插入图片描述

2.3既然指针变量是存放别人地址的变量,那什么要区分类型呢

他的类型决定了,你指向(开辟)的空间大小,也决定了他的增量大小

#include <stdio.h> int main() { 
        int a =0x1234; int *p = &a; char *c = &a;//会有一个警告,我们不需要管 printf("p=%p\n",p); printf("pc=%p\n",c); printf("p=%x\n",*p); printf("c=%x\n",*c);//取值运算符会根据指针变量类型,访问不同大小的空间 printf("++p= %p\n",++p); printf("++c = %p\n",++c); return 0; } 

在这里插入图片描述

这里我们看见,整型与字符型的区别,整型的偏移一个增加了4个字节,而字符增加了1个字节,且字符型在取值上面也出现了问题————(1个字节为8位)

3.我们为什么要用指针

3.1封装函数,实现两个数的交换
  1. 图一,传统我们交换数据的方式是,定义一个临时变量来承接,实现交换
  2. 图二,而如果我们直接是封装一个函数来进行数据传递的话,封装的函数会另外开辟出来一个地址空间(在调用函数完毕这个空间会被释放),我们通过主函数把数值传递给封装的函数,在封装函数里面完成了数值的交换,但是主函数里面的数据并没有发生任何的变化。
  3. 图三,我们通过指针直接把数据的地址传递过去,修改地址的数据,从而就可以改变主函数的值,这就是用指针的好处

在这里插入图片描述

回顾一下之前的

//已经验证没有问题 #include <stdio.h> int main() { 
        int a =5; int b =10; int tmp; printf("交换前a =%d,b =%d\n",a,b); tmp = a; a = b; b = tmp; printf("交换后a =%d,b =%d\n",a,b); return 0; } 

函数封装错误的方式

#include <stdio.h> void change(int a,int b) { 
        int tmp; tmp = a; a = b; b = tmp; } int main() { 
        int a =5; int b =10; printf("交换前a =%d,b =%d\n",a,b); change(a,b); printf("交换后a =%d,b =%d\n",a,b); return 0; } 

换了个寂寞哈哈哈哈哈

在这里插入图片描述

正确的方式

p (&a)就是存放的变量的地址 *p存放变量的地址,这个地址里面的内容是什么, &p 这个指针变量自己的一个地址

#include <stdio.h> void change(int *a,int *b)//定义一个指针变量来承接 { 
        int tmp; tmp = *a;//这里我一开始用成了取地址的a,*a的意思是取值,不是指针变量的意思 *a = *b;//而且你上面定义的就是指针变量,你怎么能用取地址呢??? *b = tmp; } int main() { 
        int a =5; int b =10; printf("交换前a =%d,b =%d\n",a,b); change(&a,&b);//把a,b的地址给传过去 printf("交换后a =%d,b =%d\n",a,b); return 0; } 

在这里插入图片描述

3.2指针指向固定的区域

单片机 armbootloader(寄存器配置)

//回顾一下 #include <stdio.h> int main() { 
        int a =10; printf("address of a is 0x%p\n ",&a); return 0; } 

在这里插入图片描述

#include <stdio.h> int main() { 
        int a =10; printf("address of a is %p\n ",&a);//指向一个固定的地址 int *p = (int *)0x000000000061FE11;//0x不能少 //升华一下 //1.无符号整形数 unsigned int *p = (unsigned int *)0x000000000061FE11; //2.这个是防止编译器认为这个地址不好然后给我们优化到别的地址,做的一项操作 //volatile unsigned int *p = (volatile unsigned int *)0x000000000061FE11; printf("address of p is %p\n",p); return 0; } 

4.通过指针引用数组,重要面试

4.1定义一个指针变量指向数组

01指向数组首元素的地址

在这里插入图片描述

02等于数组名:指向数组起始位置

在这里插入图片描述

#include <stdio.h> int main() { 
        01 /*回顾我们之前的写法 * int a =10; * int *p; * p =&a; */ 02 //现在我们变成了数组 int arr[3] = { 
       1,2,3}; int *p; 01.p = &arr[0];//指针指向数组的首地址 //还有另外一种写法 02.p =arr; //等于整个数组名也是取的数组的首地址,,,注意前面不要 + & printf("address of a is %p\n ",&a);//指向一个固定的地址 //数组名就是数组的首地址,数组的首地址就是首个元素的地址 printf("address of p is %p\n",p); return 0; } 
4.2指针增量和数组的关系

我们知道数组在一个内存中是一个连续的地址,那么指针的增量与数组之间又存在着什么关系呢?

在这里插入图片描述

#include <stdio.h> int main() { 
        int arr[3] = { 
       1,2,3}; int *p; p =arr; printf("第0元素的数值是 %d\n",*p); printf("第1元素的数值是 %d\n",*(p+1));//这里我们的+1并不是值+1,而是指针偏移,类型为整型,偏移四个字节,字符型,偏移一个字节 printf("第2元素的数值是 %d\n",*(p+2));//注意必须要加括号,下面的实操只是凑巧加起来是那个数,*p会首先结合然后再相加 return 0; } 

在这里插入图片描述

但是通过上面的方法有没有感觉很蠢,如果他有一百个元素呢?难道需要输入一百次嘛?所以我们出现了现在的for循环

#include <stdio.h> int main() { 
        int arr[3] = { 
       1,2,3}; int *p; p =arr; for(int i =0;i<3;i++){ 
        printf("第%d元素的数值是 %d\n",i,*(p+i)); printf("地址是 %p\n",(p+i)); } return 0; } 

在这里插入图片描述

4.3通过指针引用数组
#include <stdio.h> int main() { 
        int arr[3] = { 
       1,2,3}; int *p; p =arr; //方法一 for(int i =0;i<3;i++){ 
        printf("%d\n",*p++); //这个的意思是指针先*取值,然后在此基础上面再+1 } p =arr; //这里我们需要注意的是重新给指针变量p 赋值,要不然经过上面的一系列操作,指针已经跑飞了 //方法二 for(int i =0;i<3;i++){ 
        printf("%d\n",*p); p++; //指针进行 偏移 } return 0; } 

在这里插入图片描述

1.下标法

就是之前学过的数组的下标法遍历arr[ i ];

2.指针法
2.1偏移

上面的例子都是说的偏移,在此不再赘述

2.2取内容
  1. 指针当作数组名,下标法访问
  2. 数组名拿来加
///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方 #include <stdio.h> int main() { 
        int arr[3]={ 
       1,2,3}; int *p =arr; //方法一: for(int i =0;i<3;i++) { 
        printf("第%d个元素是%d\n",i,p[i]);//见怪不怪哈哈哈,他确实可以这样写 } //方法二: for(int i =0;i<3;i++) { 
        printf("第%d个元素是%d\n",i,*(p+i));//见怪不怪哈哈哈,他确实可以这样写 printf("第%d个元素是%d\n",i,*(arr+i));//见怪不怪哈哈哈,他确实可以这样写 } return 0; } 

在这里插入图片描述

数组名和指针的区别

1.arr++可行否??

///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方 #include <stdio.h> int main() { 
        int arr[3]={ 
       1,2,3};//那这个数组是一个常量 int *p =arr;//首先p是一个保存地址的变量,那他保存的地址是可以改的,这叫做变量 //方法一: for(int i =0;i<3;i++) { 
        printf("第%d个元素是%d\n",i,*p++);//,他这样可以写 } //方法二: for(int i =0;i<3;i++) { 
        printf("第%d个元素是%d\n",i,*arr++);//那这样呢?数组常量可以吗? } //编译不过,指针常量 return 0; } 

我们看到arr++是用不了的,

在这里插入图片描述

2.sizeof的使用

#include <stdio.h> int main() { 
        int arr[3]={ 
       1,2,3}; int *p =arr; printf("sizeof arr is %d\n",sizeof(arr));//一个元素四个字节,一共三个,所以12个字节 printf("sizeof arr is %d\n",sizeof(p));//在OS中,用8个字节来表示一个地址 printf("sizeof int is %d\n",sizeof(int)); printf("sizeof pointer is %d\n",sizeof(int *));//在OS中,用8个字节来表示一个地址 printf("sizeof pointer is %d\n",sizeof(char *));//在OS中,用8个字节来表示一个地址 return 0; } 

在这里插入图片描述

两种方法效率对比

在这里插入图片描述

编程案例

1.函数封装数组初始化,遍历

练习函数数组指针结合

//首先来回顾一下我们以前写的代码 #include <stdio.h> void initarr(int arr[],int size) //第二种传递形式参数的方法 void printfarr(int *parr,int size) //练习函数指针数组结合 { 
        for(int i=0;i<size;i++) { 
        printf("请输入第%d个元素\n",i+1); scanf("%d",&arr[i]) /*第二种方法 scanf("%d",parr);//因为这个本身就是地址了 指针偏移 parr++; */ } } void printfarr(int arr[],int size) { 
        for(int i=0;i<size;i++) { 
        printf("%d\n",arr[i]); } } int main() { 
        int arr[5]; int size = sizeof(arr)/sizeof(arr[0]); initarr(arr,size);//实际参数,数组的首地址:名,元素的地址 printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量 return 0; } 
#include <stdio.h> void initarr(int *parr,int size) //练习函数指针数组结合 { 
        for(int i=0;i<size;i++) { 
        printf("请输入第%d个元素\n",i+1); scanf("%d",parr++);//因为这个本身就是地址了  } } void printfarr(int *parr,int size) { 
        for(int i=0;i<size;i++) { 
        printf("%d\n",*parr++); } } int main() { 
        int arr[5]; int size = sizeof(arr)/sizeof(arr[0]); initarr(arr,size);//实际参数,数组的首地址:名,元素的地址 printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量 return 0; } 
2.将数组中的n个元素按逆序存放,函数封装

如何逆序存放呢? 第0个跟第4个换,第1个跟第3个换,2不动即可,,直接做一个临时变量,茶杯法直接交换即可

如果是奇数的话

在这里插入图片描述

那如果是偶数呢?结果同样成立

在这里插入图片描述

#include <stdio.h> void initarr(int *parr,int size) //练习函数指针数组结合 { 
        for(int i=0;i<size;i++) { 
        printf("请输入第%d个元素\n",i+1); scanf("%d",parr++);//因为这个本身就是地址了  } } void reversedarr(int *parr,int size) //练习函数指针数组结合 { 
        for(int i=0;i<size/2;i++) { 
        int j=size-1-i; int tmp; tmp = parr[i]; parr[i] = parr[j]; parr[j] = tmp; //以下是对上面的函数用指针来实现,自我感觉变抽象了哈哈哈哈 tmp = *(parr+i); *(parr+i) = *(parr+j); *(parr+j) = tmp; } putchar('\n'); } void printfarr(int *parr,int size) { 
        for(int i=0;i<size;i++) { 
        printf("%d ",*parr++);//因为这个本身就是地址了 } } int main() { 
        int arr[5]; int size = sizeof(arr)/sizeof(arr[0]); initarr(arr,size);//实际参数,数组的首地址:名,元素的地址 printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量 reversedarr(arr,size); printfarr(&arr[0],size); return 0; } 

5.指针与二维数组

父子数组,为了研究清楚地址的概念,把二维回归到一维数组

二维数组本质还是数组,不同点是数组元素还是个数组(子数组),以往的我们 int arr[ ]={1,2,3};如果arr+1 = 1;那么这次我们,如果是a[0]+1呢?如下图,a[0]是第一行第一列的地址,a[0]+1=3;

在这里插入图片描述

首地址的表示:1.数组名 2.首个元素的地址。

下面我们来思考几个问题?? 1.a是谁的地址 2.a[0]又是谁的地址 3.a跟 (a+0)呢?

  1. a是父数组,地址是第一整行的地址,a+1向下偏移一行,4*4=16个字节
  2. a[0]是子数组,地址为第一行第一列的地址,
  3. 以往的 int *p = arr; int arr[ ]={1,2,3}, 我们知道指针变量是存放地址的变量,那么arr就是数组的首地址, *arr就是取内容之后,即 *arr=1;所以 *a就是第一行第一列的地址,与a[0]等价,也与 *(a+0)等价

在这里插入图片描述

那么a[0]+1又是什么意思呢?

  1. a[0]+1第0行第一列的地址,是地址的意思,,*(a+0)+1
  2. 也可以说是第0个子数组的第1个元素的地址
  3. 而第0个子数组的第1个元素表示方式是a[0] [1],不要乱

在这里插入图片描述

//一定要记住arr[0]=*arr=*(arr+0) #include <stdio.h> int main() { 
        int arr[3][4] ={ 
       { 
       1,2,3,4};{ 
       5,6,7,8};{ 
       9,10,11,12}}; printf("arr是父亲数组:%p,偏移1后是%p\n",arr,arr+1); printf("arr[0]是子数组:%p,偏移1后是%p\n",arr[0],arr[0]+1); printf("arr[0]是子数组:%p,偏移1后是%p\n",*arr,*(arr+0)+1); return 0; } 

在这里插入图片描述

可以看到父数组偏移了16个字节,子数组是4个字节

树立认知

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

//一定要记住arr[0]=*arr=*(arr+0) #include <stdio.h> int main() { 
        int arr[3][4] ={ 
       { 
       1,2,3,4};{ 
       5,6,7,8};{ 
       9,10,11,12}}; for(int i= 0;i<3;i++){ 
        for(int j =0;j<4;j++) { 
       //这是我们以前的写法,工作后也可以这样写,但面试就要用指针了 printf("address:0x%p,data:%p\n",&arr[i][j],arr[i][j]); //arr[0]+0 = &arr[0][0] printf("address:0x%p,data:%p\n",arr[i]+j,*(arr[i]+j)); //笔试题考,这个就是指针的偏移来取到地址 printf("address:0x%p,data:%p\n",*(arr+i)+j,*(*(arr+i)+j)); //把arr[0]=*(arr+0)替代,是不是傻眼了 } } return 0; } 

在这里插入图片描述

小总结(嵌入式工程师笔试题会考)

在这里插入图片描述

6.数组指针

数组指针,一个指向数组的指针

指针数组,一堆指针组成的一个数组

在这里插入图片描述

#include <stdio.h> int main() { 
        int arr[3][4] ={ 
       { 
       11,22,33,44},{ 
       55,66,77,88},{ 
       44,55,66,77}}; //那么之前我们说的arr++,是否可以呢?arr++增加的数值是一整个数组(arr[0][0],arr[1][0]), //而p++是单个偏移(arr[0][0],arr[0][1]) int i,j; int *p; //01. p = arr; //02. p = &arr[0][0]; for(i=0;i<3;i++) { 
        for(j=0;j<4;j++) { 
        printf("%p\n",arr++); printf("%p\n",p++); } } return 0; } 

在这里插入图片描述

例题

输出二维数组任意行列的数

#include <stdio.h> void tips_input(int *hang,int *lie) { 
        printf("输入你想要的行列\n"); scanf("%d%d",&hang,&lie); puts("done! "); } int get_data(int (*p)[4],int hang,int lie) { 
        int data; data = *(*(p+hang)+lie); return data; //第二种简单的写法 return arr[hang][lie]; } int main() { 
        int arr[3][4] = { 
       { 
       11,22,33,44},{ 
       55,66,77,88},{ 
       99,01,02,03}}; int hang,lie; int data; //1.提醒用户输入想要的行列值 tips_input(&hang,&lie); //2.找到行列值所对应的数 data = get_data(arr,hang,lie); //3.打印出来 printf("第%d行,第%d列的数为%d",hang,lie,data); return 0; } 

7.函数指针

指向一个函数地址的指针,类似于一个函数的地址入口,定义“函数地址”,数组名是一个地址,那么函数名也是一个地址

在这里插入图片描述

1.如何定义一个函数指针

跟普通变量一样,

int a; int *p; char c; char *p; int getData(int a, int b);//函数传参 int (*p)(int a,int b);//函数指针的调用 
//如何使用函数指针 #include <stdio.h> void printf_hello() { 
        printf("欢迎来到我的世界\n"); } int main() { 
        printf_hello();//这是以前我们调用函数 void (*p)(); //1.定义一个函数指针 p = printf_hello; //2.指针指向函数 (*p)(); //3.把函数指针里面的内容调用出来 //函数调用概念和变量一样 return 0; } 
//OK,现在我有两个函数,类型不同,有两种访问的方式,1.直接访问(函数调用) 2.间接访问(函数指针) //下面来着重介绍一下函数指针的用法 #include <stdio.h> void printf() { 
        puts("欢迎来到我的世界"); } int idata(int dataone) { 
        return ++dataone; } int main() { 
        //1.定义指针 2.指针指向函数 3.调用函数指针 void (*p1)(); int (*p2)(int a);//注意实参也不要丢了啊啊啊啊(原先丢了一次编译会出错) p1 = printf; p2 = idata; (*p1)(); printf("将P2呈现出来是%d\n",(*p2)(12)); return 0; } 
2.好用之处

根据程序运行过程的不同情况,调用不同的函数(Java接口)

练习题

在这里插入图片描述

#include <stdio.h> int getMax(int a,int b) { 
        return a>b?a:b; } int getMin(int a,int b) { 
        return a<b?a:b; } int getSum(int a,int b) { 
        return a+b; } int dataHandler(int a,int b,int(*p)(int ,int))//我们强调的是函数类型,而具体的形参名如果用不到的话可以不定义 { 
        int ret; ret = (*p)(a,b); return ret; } int main() { 
        int a = 10; int b = 20; int cmd; int ret; //1.定义函数指针 int (*pfunc)(int ,int ); //2.根据你输入的值来决定指针指向那个函数 printf("请输入1(求大者),2(求小者),3(求和)\n"); scanf("%d",&cmd); switch(cmd) { 
        case 1: pfunc = getMax; break; case 2: pfunc = getMin; break; case 3: pfunc = getSum; break; default: printf("输入错误。@请输入1(求大者),2(求小者),3(求和)"); //exit(-1); break; } //3.将函数指针调用出来 // ret = (*pfunc)(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下 ret = dataHandler(a,b,pfunc); printf("result of is %d",ret); return 0; } 

回调函数的底层逻辑,

  1. 线程 int pthread_create(pthread_t *id,const pthread_attr_t attr, voidstart_rtn)(void), void *restrict arg);
  2. QT的信号与槽

8.指针数组

1.定义,注意和数组指针的区别(面试会考)

简单来说就是,一个由指针组成的数组,

在这里插入图片描述

//数组指针的使用 #include <stdio.h> int main() { 
        int a,b,c,d; a = 10; b = 20; c = 30; d = 40; int *p[4] = { 
       &a,&b,&c,&d}; for(int i = 0;i<4;i++) { 
        printf("%d\n",*p[i]); } return 0; } 
2.函数指针的使用
//函数指针数组 #include <stdio.h> int getMax(int a,int b) { 
        return a>b?a:b; } int getMin(int a,int b) { 
        return a<b?a:b; } int getSum(int a,int b) { 
        return a+b; } int main() { 
        int a = 10; int b = 20; int cmd; int ret; //1.定义函数指针 //这个由上面得来,那我如果想要函数指针数组呢? int (*pfunc[3])(int ,int ) ={ 
       getMax,getMin,getSum}; //3.将函数指针调用出来 for(int i = 0;i<3;i++) { 
        ret = (*pfunc[i])(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下 printf("result[%d] of is %d",i,ret); } return 0; } 

9.指针函数

一个返回值是指针的函数

概念

在这里插入图片描述

练习题:在这里插入图片描述

#include <stdio.h> int* get_pos_person(int pos,int (*p)[4]) { 
        int *p2; p2 = (int*)(p+pos);//因为这两个变量的类型不一样,所以要强转一下 return p2; } int main() { 
        int arr[3][4] ={ 
       { 
       11,22,33,44},{ 
       15,23,54,84},{ 
       61,51,48,25}}; int pos; int *ppos; printf("请输入想看的学生号数(0,1,2):\n"); scanf("%d",&pos); ppos = get_pos_person(pos ,arr); for(int i =0;i<4;i++) { 
        printf("他的成绩分别为%d\n",*(ppos++)); } return 0; } 

10.二级指针

一级指针指向一个变量,存放他的地址;那么这个一级指针它本身也有一个地址,如果我再定义一个指针来存放这个一级指针的地址,那么这个指针就是二级指针,以此类推可延伸至三级乃至多级指针

认知考虑的时候,其实所有东西跟一级指针一样,写法:int p;

#include <stdio.h> int main() { 
        int data = 100; int *p = &data; int **p2 = &p;//二级指针 printf("data的值为%d\n",data); printf("data本身的地址为%p\n",&data); printf("p存放的值为%d\n",*p); printf("p存放的值为%p\n",p); printf("p的地址为%p\n",&p); printf("p2存放的值为%p\n",*p2); printf("p2存放的值为%p\n",p2); printf("p2的地址为%p\n",&p2); printf("p2存放的地址的值(也就是一级指针存放的值为)%d\n",**p2); return 0; } 

在这里插入图片描述
差别就是保存的是指针变量的地址。当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样

在这里插入图片描述

//1.如果我们不返回一个int型指针呢?只有一个空类型,应该怎么传参呢?(此代码为上面已用过的) //2.当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样 #include <stdio.h> //int* get_pos_person(int pos,int (*p)[4]) void get_pos_person(int pos,int (*p)[4],int **ppos) { 
        //第一个括号是强转的意思。,转为指针变量 *ppos = (int *)(p+pos);//我们修改的ppos的地址,用一个二级指针来承接一级指针的地址然后改动一级指针的地址 } int main() { 
        int arr[3][4] ={ 
       { 
       11,22,33,44},{ 
       15,23,54,84},{ 
       61,51,48,25}}; int pos; int *ppos; printf("请输入想看的学生号数(0,1,2):\n"); scanf("%d",&pos); get_pos_person(pos ,arr,&ppos); for(int i =0;i<4;i++) { 
        printf("他的成绩分别为%d\n",*(ppos++)); } return 0; } 

二级指针不能简单粗暴指向二维数组

在这里插入图片描述

11.总结

中小公司大概率考题

在这里插入图片描述

第七章 字符串

男儿何不带吴钩?收取关山五十州。

1.字符串的引入以及注意事项

1.1字符数组,与数组差不多

注意单个字符用单引号‘ ’ 字符串要用双引号“ ”

#include <stdio.h> int main() { 
        char c = 'h';//定义的一个单字符,,这是以前的写法 //一、字符串的定义 //1.以往我们数组的书写方式 int data[] = { 
       1,2,3,4,5}; //2.那么如果换成字符串呢? char cdata1[] = { 
       'h','e','l','l','o'};//1.很蠢的一个方式 char cdata2[] = "hello"; //2.对上面进行改进的第二个方法 char *pchar = "hello"; //3.用指针的方法来定义字符串 //二、遍历  //1.这种遍历字符串的方式依旧有些蠢 for(int i = 0;i<5;i++) { 
        printf("cdata = %c\n",cdata[i]); printf("pchar = %c\n",*(pchar+i));//指针的地址偏移,然后再取内容 } //2.第二种方法 printf("%s",pchar);//%s是输出字符串格式声明,第一章有讲过 putchar('\n');//输出单字符 puts(pchar);//直接打出字符串 return 0; } 
//这两个定义字符串的区别,,我如果想改里面单个的元素,第一个会成功,而指针存储的方式不会 //1.这个是字符串变量,数值可以修改  char cdata2[] = "hello"; //2.对上面进行改进的第二个方法 cdata2[3] = 'm'; //这个会修改成功 //2.字符串常量,不允许被修改 char *pchar = "hello"; //3.用指针的方法来定义字符串 *pchar = 'p'; //不会报错,但是会卡在这里 

野指针

char *p; //野指针,并没有明确的内存指向,很危险 *p = 'a'; //会报错 //注意指针的操作 1.保存地址可以,修改指向,指向字符串常量的地址空间 2.对野指针的内存空间操作不行 

2.字符串的内存存放方式及结束标志

//和整形数组在存储上的区别  #include <stdio.h> int main() { 
        int len1,len2; //1.这个数组的大小是5 int arr[] = { 
       1,2,3,4,5}; len1 = sizeof(arr)/sizeof(arr[0]); printf("arr的大小%d\n",len1); //2.而字符串的结束标志是'\0'(系统会自己加上这个标志,告诉说这个字符串已经结束),所以大小是6 int carr[] = { 
       "hello"}; len2 = sizeof(carr)/sizeof(carr[0]); printf("carr的大小%d\n",len2); return 0; } 

3.sizeof 与 strlen的区别

//1.sizeof计算的是整个的长度 2.strlen计算的是字符中的有效长度,记住是有效长度 #include <stdio.h> int main() { 
        char arr[] = { 
       "hello"}; printf("sizeof的大小\n",sizeof(arr)); //大小为6 printf("strlen的大小\n",strlen(arr)); //这个大小是5 char arr[50] = { 
       "hello"}; //如果是定死的呢?我们知道不足的会自动补0 printf("sizeof的大小\n",sizeof(arr)); //大小为50 这个就没有\0了 printf("strlen的大小\n",strlen(arr)); //这个大小是5 return 0; } 

在这里插入图片描述

4.动态开辟字符串(难点)

#include <stdio.h> #include <string.h> #include <stdilb.h> int main() { 
        / char *p; //野指针,并没有明确的内存指向,很危险 *p = 'a'; //会报错 */ //一、那么如何解决这种情况呢??? //1.我们引入第一个C库函数malloc,, 2.函数原型 void *malloc(size_t size)  //3. C库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。 char *p; p = (char *)malloc(1); //将malloc强转成char型,然后开辟出1个字节的内存空间,给到P *p = 'a'; puts(p);//这个时候我们就可以访问出来p了 free(p); //一般我们在清空指针内的内存空间之后要把P = NULL; / 首先函数是存放在栈里面的,在调用完成之后会自动的释放内存 但是malloc函数是存放在堆里面的,这样就会存在一个风险,如果他存在一个while循环里面,就会把内存耗光 上面在malloc(1)之后,又有开辟出12个字节空间malloc(12),这个时候原先的malloc(1)就变成了悬挂指针(野指针的一种) 这个时候我们就需要释放出来原本不需要的内存空间,就用到了free函数。 1. C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。 2.释放,防止内存泄露 3.防止悬挂指针(野指针的一种) */ //二、那么如果我想再一次给他开辟一些空间存储另外一些数据呢? p = (char *)malloc(12); //又开辟了12个空间 strcpy(p,"helloworlld"); //这个函数第一个是放到哪里?第二个是放进什么? //三、扩容函数 /* 函数原型 void *realloc(void *ptr, size_t size),,,扩容,, C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。 / //我们又放进很多字符,而这时候已经越界了,超出12个了,怎么办? p = (char *)malloc(12); strcpy(p,"helloworlld"); int len = strlen("helloworlld"); int newlen = len - 12 + 1;//减去原本的,再加上一个\0 realloc(p,newlen); //四、清空函数 memset(p,'\0',12);//将上面新开辟的12个内存空间来赋为\0  return 0; } 

在这里插入图片描述

在这里插入图片描述
指针指向的字符串,呈现时只能显示一个

5.几种字符串常用的API

#include <stdio.h> int main() { 
        / 一、输出字符串 1.puts(); 可自动换行 2.printf("%s",p); */ char *p = "hello,world"; puts("请输入字符串"); puts(p); printf("p的字符串为%s\n",p); /* 二、获取字符串 1.scanf("%s",p); 2.gets(); 函数原型char * gets ( char * str ); 因为本函数可以无限读取,易发生溢出。如果溢出,多出来 的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破 坏一个或多个不相关变量的值 */ char str[128] = { 
       '\0'}; scanf("%s",&str); puts(str); / 三、计算长度 1.strlen; / return 0; } 
//自己实现字符串拷贝函数 /* 1.strcpy 它的函数原型>>第一个为目标,第二个为源* 通俗点来说就是,1.需要放在哪里,2.放什么 char *strcpy(char* dest, const char *src); */ / 2.strncpy 它的函数原型>>第一个为目标,第二个为源,第三个是需要复制的前几个字节* char *strncpy(char *dest, const char *src, int n); 表示把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest */ #include <stdio.h> //第一种,简单;第二种,比较常见;第三种,炫技术 //第一种写法 char *myStrcpy(char *des,char *src) { 
        if(des == NULL || src == NULL)//如果是空的话就不往下走了 { 
        return NULL; } char *bak =des;//将目标地址保存下来 while(*src != '\0') { 
        *des = *src; *des++; *src++; } *des = '\0';//最后给他加个\0收尾 //之前写成了*src ='\0';会发生段错误 return bak; } //第二种写法 char *myStrcpy2(char *des,char *src) { 
        if(des == NULL || src == NULL)//如果是空的话就不往下走了 { 
        return NULL; } char *bak =des;//将目标地址保存下来 while(*src != '\0') { 
        *des++ = *src++;//这里进行了缩减 } *des = '\0';//最后给他加个\0收尾 return bak; } /* 网上比较多的是这种形式 char *myStrcpy2(char *des,char *src) { while(*src != '\0') { *des++ = *src++;//这里进行了缩减 } } / //第三种 char *myStrcpy3(char *des,char *src) { 
        if(des == NULL || src == NULL)//如果是空的话就不往下走了 { 
        return NULL; } char *bak =des;//将目标地址保存下来 while((*des++ = *src++)!= '\0'); *des = '\0';//最后给他加个\0收尾 return bak; } //srtncpy char *myStrncpy(char *des,char *src,int count) { 
        if(des == NULL || src == NULL)//如果是空的话就不往下走了 { 
        return NULL; } char *bak =des;//将目标地址保存下来 while(*src != '\0'&& count>0) { 
        *des++ = *src++;//这里进行了缩减 count--; } //那如果我一共只有15个字符串,而我填的拷贝到17个,前条件先到达,应该怎么处理呢? if(count>0){ 
        while(count>0){ 
        count--; *des++='\0'; } return des;//因为进入这个循环之后结尾已经赋值为\0了,下面的赋值不需要再走了 } *des = '\0';//最后给他加个\0收尾 return bak; } int main() { 
        char str[128] ={ 
       '\0'}; char *p = "hello,world"; printf("%c\n",*p++); myStrcpy(str,p);//这里的一整个指针p会将字符串全部传过去 myStrncpy(str,p,5); puts(str); return 0; } 

6.C语言实现断言函数assert

assert 的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。

使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。

在调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下:

#include  #define NDEBUG  #include //其表达的意思就是,程序在我的假设条件下,能够正常良好的运作,其实就相当于一个 if 语句: 但是这样写的话,就会有无数个 if 语句,甚至会出现,一个 if 语句的括号从文件头到文件尾,并且大多数情况下, 我们要进行验证的假设,只是属于偶然性事件,又或者我们仅仅想测试一下,一些最坏情况是否发生, 所以这里有了 assert()。 assert 宏的原型定义在 assert.h 中,其作用是如果它的条件返回错误,则终止程序执行。 if(假设成立) { 
        程序正常运行; } else { 
        报错&&终止程序!(避免由程序运行引起更大的错误) } 
#include <assert.h> #include <stdio.h> int main() { 
        int a; char str[50]; printf("请输入一个整数值: "); scanf("%d", &a); assert(a >= 10); printf("输入的整数是: %d\n", a); printf("请输入字符串: "); scanf("%s", str); assert(str != NULL); printf("输入的字符串是: %s\n", str); return(0); } 

在这里插入图片描述

在这里插入图片描述

7.字符串拼接strcat的使用及实现

可以参考博客

#include <stdio.h> #include <string.h> int main() { 
        char str[128] = "hello,"; char *p = "world"; char *p2; p2 = strcat(str,p);//他的返回值也是拼接成的字符串 puts(str); puts(p2); return 0; } 

在这里插入图片描述

//自己实现strcat函数 把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。 要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针 #include <stdio.h> #include <assert.h> #include <string.h> //第一种写法 char *myStrcat(char *des,char *src) { 
        assert(des!=NULL && src!=NULL); char *bak = des; while(*des != '\0'){ 
        *des++; } while((*des++=*src++) !='\0'); *des ='\0'; return bak; } //第二种写法 char *myStrcat2(char *des,char *src) { 
        assert(des!=NULL && src!=NULL); char *bak = des; strcpy((des+strlen(des)),src);//把des向后偏移几个后再拷贝过来 return bak; } //第三张写法,把while换成了for循环 char *myStrcat3(char *des,char *src) { 
        assert(des!=NULL && src!=NULL); char *bak = des; for(;*des!='\0';des++); while((*des++=*src++) !='\0'); *des ='\0'; return bak; } int main() { 
        char str[128] = "hello,"; char *p = "world"; char *p2; p2 = myStrcat(str,p);//他的返回值也是拼接成的字符串 puts(str); puts(p2); return 0; } 

8.字符串比较函数strcmp使用及实现

/ 1.strcmp int strcmp(const char *str1,const char *str2); 若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数 / / 2.strncmp int strncmp ( const char * str1, const char * str2, size_t n ) * 功能是把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同, 则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。 */ //查找子字符 / 1.strchr char *strchr(const char *str, int c); 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置 / //查找子字符 / 1.strchr char *strchr(const char *str, int c); 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置 / //查找子串 / 1.strstr char *strstr(char *str1, const char *str2);* 返回值:若str2是str1的子串,则返回str2在str1的首次出现的地址; 如果str2不是str1的子串,则返回NULL / //字符串分割 / 1.strtok char *strtok(char *str, const char *delim)* 分解字符串 str 为一组字符串,delim 为分隔符 特别要注意分割处理后原字符串 str 会变,原字符串的改动是切分符原位置均更改为 '\0' / 

在这里插入图片描述

//自己实现strcmp #include <stdio.h> #include <string.h> int mystrcmp(char *des,char *src) { 
        int tmp; while(*des && *src && (*des == *src)) { 
        *des++; *src++; } tmp = *des -*src; if(tmp<0){ 
        tmp = -1; } if(tmp>0){ 
        tmp = 1; } if(tmp==0){ 
        tmp =0; } return tmp; } int main() { 
        char str[32] = "hello,world"; char *p = "hello,worle"; int data; data =mystrcmp(str,p); printf("%d\n",data); return 0; } 

第八章 结构体

完结散花,C语言全部章节更新完毕

1.初识

1.1为什么要用结构体

整型数,浮点型数,字符串是分散的数据表示,有时候我们需要用很多类型的数据来表示一个整体,比如学生信息

在这里插入图片描述

类比与数组:数组是元素类型一样的数据集合。如果是元素类型不同的数据集合,就要用到结构体了。

1.2定义一个结构体

在这里插入图片描述

在声明的同时,定义变量,尽量少用

在这里插入图片描述

在这里插入图片描述

1.3初始化一个结构体变量并引用

在这里插入图片描述

//初始化结构体 #include <stdio.h> #include <string.h> struct Student { 
        int num; char name[32]; char sex; int age; double score; char addr[32]; }; //注意这里的分号不要丢 int main() { 
        int a; //struct Student (这里可以看做 int) stu1(这个就属于a,变量名) struct Student stu1 = { 
       2,"张三",'g',17,99.5,"北京"};//01 //001.那么如何赋值呢?两种赋值方式 1.int a=10 2.int a; a=10;对比以前的 struct Student stu2;//02 stu2.num = 2;//1.点运算符来访问结构体中的成员变量(域) stu2.age = 18;//2.结构体里面的成员变量不是非要用上的,只用一部分也是可以的 stu2.score = 88.8; strcpy(stu2.name,"李四"); strcpy(stu2.addr,"湖南"); //002.那么如何去引用呢? printf("学号:%d,年龄:%d,分数:%.2f,名字:%s,地址:%s\n",stu2.num,stu2.age,stu2.score,stu2.name,stu2.addr); return 0; } 
1.4例题

例题:输入两个学生的名字,学号,成绩,输出成绩高的学生的信息

重点认知:结构体没什么特殊的,只是把变量藏在结构体里面,而内部的变量,以前学习的东西是通用的,只是“触达的方式”不同

#include <stdio.h> #include <string.h> struct Student { 
        int num; char name[32]; char sex; int age; double score; char addr[32]; }; int main() { 
        int a; //int tmp; struct Student stu1 = { 
       2,"张三",'g',17,9,"北京"};//01 struct Student stu2;//02 struct Student max;//注意要把这个定义出来 stu2.num = 2; stu2.age = 18; stu2.score = 88.8; strcpy(stu2.name,"李四"); strcpy(stu2.addr,"湖南"); max = stu1;//做一个变量的话,可以省写很多,要不然两个需要一一打印 if(stu1.score<stu2.score){ 
        max = stu2; } printf("学号:%d,年龄:%d,分数:%.2f,名字:%s,地址:%s\n", max.num,max.age,max.score,max.name,max.addr); return 0; } 

2.结构体数组

#include <stdio.h> #include <string.h> struct Student { 
        int num; char name[32]; char sex; int age; double score; char addr[32]; }; int main() { 
        int arr[3] ={ 
       1,2,3}; int i; int len; // len = sizeof(arr)/sizeof(arr[0]); //类比数组来做结构体的数组 struct Student arr2[3] = { 
        { 
       1,"张三",'g',17,9,"河北"}, { 
       2,"李四",'g',18,9,"广东"}, { 
       3,"王五",'g',17,9,"湖南"} }; len = sizeof(arr2)/sizeof(arr2[0]); for(i=0;i<len;i++) { 
        printf("学号:%d,年龄:%d,分数:%.2f,名字:%s,地址:%s\n", arr2[i].num,arr2[i].age,arr2[i].score,arr2[i].name,arr2[i].addr); } return 0; } 

3.应用练习:选票系统

#include <stdio.h> #include <string.h> struct xuanmin{ 
        int tickets; char name[32]; }; int main() { 
        int total = 5; char person[32]; int mark; int qipiao = 0; int j; //第二个思路我想要选择出弃票的有几个,然后票数最高的是谁 struct xuanmin arr[3]; struct xuanmin max; max.tickets = 0; int len =sizeof(arr)/sizeof(arr[0]); //1.输入候选人 for(int i =0;i<len;i++){ 
        arr[i].tickets = 0; printf("请输入第%d候选人的名字\n",i+1); scanf("%s",arr[i].name); } //2.你要投那个候选人(一人一票,共五个人) for(int i = 0;i<total;i++){ 
        printf("请输入你想投的给谁一票\n"); memset(person,'\0',sizeof(person)); scanf("%s",person); mark = 0; for(int j =0;j<3;j++){ 
        if(strcmp(person,arr[j].name)==0){ 
        arr[j].tickets++; mark = 1; } } if(mark == 0){ 
        qipiao++; } } //3.公布结果 for(int i=0;i<3;i++){ 
        printf("%s候选人共%d票\n",arr[i].name,arr[i].tickets); } for(int i =0;i<len;i++){ 
        if(max.tickets<arr[i].tickets){ 
        max = arr[i];//这是一个出彩的点,直接用一个票数换了整个结构体  j = i; } } printf("%s候选人以%d票当选,弃票%d人\n",max.name,max.tickets,qipiao); return 0; } 

在这里插入图片描述

4.结构体指针

1.概念引入
  1. 回忆:指针就是地址 指针变量就是存放地址的变量
    结构体也是变量
    变量访问有两种方式 : 1.变量名 2.地址
    之前案例,是用变量名访问


  2. 通过结构体变量地址来访问该结构体
    需要一个变量来保持这个地址:
    这和之前说的指针,其实是一样的
    只是指针类型是结构体


  3. int a; struct Test t;
    int *p; struct Test *p;
    p = &a; p = &t

#include <stdio.h> #include <string.h> struct student{ 
        char name; int numble; }; int main() { 
        //我们对照以前的 int a; int *p =&a; char c; char *p2=&c; struct student stu1 ={ 
       'c',21}; struct student *stu = &stu1; //结构体指针的定义 //那我如果想要访问里面的数据呢? printf("%d\n",stu1.numble);//普通的变量名访问 printf("%C\n",stu1.name); //用最常见的.运算符 stu ->name ='d'; printf("%c\n",stu->name[32]); //间接的地址访问 printf("%d\n",stu->numble);//用->运算符 } 

在这里插入图片描述

2.小应用
1.指针在结构体数组中的偏移
 #include <stdio.h> #include <string.h> struct Student { 
        int num; char name[32]; char sex; int age; double score; char addr[32]; }; int main() { 
        int arr[3] ={ 
       1,2,3}; int i; int len; // len = sizeof(arr)/sizeof(arr[0]); //类比数组来做结构体的数组 struct Student arr2[3] = { 
        { 
       1,"张三",'g',17,9,"河北"}, { 
       2,"李四",'g',18,9,"广东"}, { 
       3,"王五",'g',17,9,"湖南"} }; struct Student *p = arr2; len = sizeof(arr2)/sizeof(arr2[0]); for(i=0;i<len;i++) { 
        printf("学号:%d,年龄:%d,分数:%.2f,名字:%s,地址:%s\n", p->num,p->age,p->score,p->name,p->addr);//指针指向结构体 p++;//指针偏移 } return 0; } 
//用结构体指针来替换原先的选票系统 #include <stdio.h> #include <string.h> struct xuanmin{ 
        int tickets; char name[32]; }; int main() { 
        int total = 5; char person[32]; int mark; int qipiao = 0; //第二个思路我想要选择出弃票的有几个,然后票数最高的是谁 struct xuanmin arr[3]; struct xuanmin max; struct xuanmin *p = arr; max.tickets = 0; int len =sizeof(arr)/sizeof(arr[0]); //1.输入候选人 for(int i =0;i<len;i++){ 
        arr[i].tickets = 0; printf("请输入第%d候选人的名字\n",i+1); scanf("%s",p->name); p++; } p = arr; //2.你要投那个候选人(一人一票,共五个人) for(int i = 0;i<total;i++){ 
        p =arr; printf("请输入你想投的给谁一票\n"); memset(person,'\0',sizeof(person)); scanf("%s",person); mark = 0; for(int j =0;j<3;j++){ 
        if(strcmp(person,p->name)==0){ 
        (p->tickets)++; mark = 1; } p++; } if(mark == 0){ 
        qipiao++; } } p = arr; //3.公布结果 for(int i=0;i<3;i++){ 
        printf("%s候选人共%d票\n",p->name,p->tickets); p++; } p = arr; max = arr[0]; for(int i =1;i<len;i++){ 
        if(max.tickets<p->tickets){ 
        max = arr[i];//这是一个出彩的点,直接用一个票数换了整个结构体  } p++; } printf("%s候选人以%d票当选,弃票%d人\n",max.name,max.tickets,qipiao); return 0; } 

5.共用体/联合体

1.概念引入
  1. 有时候同一个内存空间存放类型不同,不同类型的变量共享一块空间
  2. 结构体元素有各自单独空间
    共用体元素共享空间,空间大小有最大类型确定
  3. 结构体元素互不影响
    共用体赋值会导致覆盖
#include <stdio.h> #include <string.h> //1.联合体的大小取决于最大的整数数 //2.联合体里面的变量值都是在同一个地址里面存放着 struct Test{ 
        int idata; char cdata; double ddata; }; union Testu{ 
        int idata; char cdata; double ddata; }; int main() { 
        struct Test t; union Testu u; printf("结构体的大小为%d\n",sizeof(t)); printf("联合体的大小为%d\n",sizeof(u)); printf("idata:%p\n",&t.idata); printf("cdata:%p\n",&t.cdata); printf("ddata:%p\n",&t.ddata); printf("idata:%p\n",&u.idata); printf("cdata:%p\n",&u.cdata); printf("ddata:%p\n",&u.ddata); return 0; } 

在这里插入图片描述

#include <stdio.h> #include <string.h> //1.联合体的大小取决于最大的整数数 //2.联合体里面的变量值都是在同一个地址里面存放着 struct Test{ 
        int idata; char cdata; double ddata; }; union Testu{ 
        int idata; char cdata; double ddata; }; int main() { 
        struct Test t; union Testu u; printf("结构体的大小为%d\n",sizeof(t)); printf("联合体的大小为%d\n",sizeof(u)); //3.共同体的数据会被覆盖 t.idata = 10; t.cdata = 'a'; printf("idata:%p,%d\n",&t.idata,t.idata); printf("cdata:%p,%d\n",&t.cdata,t.cdata); printf("ddata:%p\n",&t.ddata); u.idata = 20; u.cdata = 'a'; printf("idata:%p,%d\n",&u.idata,u.idata); printf("cdata:%p,%d\n",&u.cdata,u.cdata); printf("ddata:%p\n",&u.ddata); return 0; } 

在这里插入图片描述

6.宏定义define

宏定义define的新变量在左边,

  • 关键字:#define
  • 用途:用一个字符串代替一个数字(字符也可以),便于理解,防止出错;提取程序中经常出现的参数,便于快速修改定义
  • 宏定义: #define ABC 12345
  • 引用宏定义: int a = ABC; //等效于int a = 12345;

在这里插入图片描述

7.typedef

给变量类型结合,一般跟结构体配合较多

  • 关键字:typedef
  • 用途:将一个比较长的变量类型名换个名字,便于使用
  • 定义typedef: typedef unsigned char uint8_t;
  • 引用typedef: uint8_t a; //等效于unsigned char a;
  • 注意typedef不需要加分号,define需要加分号
#include <stdio.h> struct student{ 
       char name;int sex} stu1 int main() { 
        return 0; } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128047.html

(0)
上一篇 2025-09-03 15:26
下一篇 2025-09-03 15:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信