大家好,欢迎来到IT知识分享网。
【全篇】C语言从入门到入土
文章目录
- 【全篇】C语言从入门到入土
第一章 前言
如何去学习,学习方法论
1.看视频学习
- 不要拉进度条
- 遇到熟悉的知识点或者二刷可以倍速看
2.视频中的代码
- 理解
- 照着打(形成关键词记忆,肌肉记忆,,,,很重要)
- 默写打
- 编译出错不要怕,有错误提示,,,要积累,,然后解决
- 不要丢掉没写对的代码,一定要调试正确为止,,,
- 错误代码也要积累,,,多多总结——(写博文,,CSDN,云笔记,空间等等)
—————刚开始low没关系,目的是能编程能做东西
第二章 初识
1.代码编译工具
配置环境变量———-目的是命令终端中的任意文件夹能识别gcc指令
安装gcc工具———–mingw—下载地址(http://www.mingw-w64.org/doku.php)
博客参考:https://blog.csdn.net/Leo_LiangXuYuan/article/details/
使用
- 打开命令终端cmd
- cd指令跳到代码文件夹
- 编译和运行,-o选项,指定生成的程序名字
- gcc test.c -o pro
指令 c文件 生成选项 新程序名字(a.exe) - gcc test.c -g 让你的程序变成可调试(不需要了解那么深了,一般在程序崩的莫名其妙,不知道哪里出了问题可以试一试)
- gdb a.exe 之后输入 r 进入待运行状态(之后再运行就可以看到是哪里出现了问题了)
- 退出的话输入 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是自动换行的
如果是小数或者字符呢??自行体会
调用过程
- 内存空间
- 值传递
- 值返回
(如果函数返回类型是void,函数体可以不用加return,返回值要注意类型,如果类型不同,可能会发生强制转换影响结果或编译警告)
- 内存释放(如果想要发生值改变,后面可以学到指针之后可以用指针传递地址)
函数调用的条件
- 函数已被定义
- 调用库函数
- 函数的声明
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封装函数,实现两个数的交换
- 图一,传统我们交换数据的方式是,定义一个临时变量来承接,实现交换
- 图二,而如果我们直接是封装一个函数来进行数据传递的话,封装的函数会另外开辟出来一个地址空间(在调用函数完毕这个空间会被释放),我们通过主函数把数值传递给封装的函数,在封装函数里面完成了数值的交换,但是主函数里面的数据并没有发生任何的变化。
- 图三,我们通过指针直接把数据的地址传递过去,修改地址的数据,从而就可以改变主函数的值,这就是用指针的好处
回顾一下之前的
//已经验证没有问题 #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取内容
- 指针当作数组名,下标法访问
- 数组名拿来加
///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方 #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)呢?
- a是父数组,地址是第一整行的地址,a+1向下偏移一行,4*4=16个字节
- a[0]是子数组,地址为第一行第一列的地址,
- 以往的 int *p = arr; int arr[ ]={1,2,3}, 我们知道指针变量是存放地址的变量,那么arr就是数组的首地址, *arr就是取内容之后,即 *arr=1;所以 *a就是第一行第一列的地址,与a[0]等价,也与 *(a+0)等价
那么a[0]+1又是什么意思呢?
- a[0]+1第0行第一列的地址,是地址的意思,,*(a+0)+1
- 也可以说是第0个子数组的第1个元素的地址
- 而第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; }
回调函数的底层逻辑,
- 线程 int pthread_create(pthread_t *id,const pthread_attr_t attr, void(start_rtn)(void), void *restrict arg);
- 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.变量名 2.地址
之前案例,是用变量名访问 - 通过结构体变量地址来访问该结构体
需要一个变量来保持这个地址:
这和之前说的指针,其实是一样的
只是指针类型是结构体 - 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.概念引入
- 有时候同一个内存空间存放类型不同,不同类型的变量共享一块空间
- 结构体元素有各自单独空间
共用体元素共享空间,空间大小有最大类型确定 - 结构体元素互不影响
共用体赋值会导致覆盖
#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