【全篇】C语言从入门到入土
橘猫.exe 2024-10-10 13:05:02 阅读 84
【全篇】C语言从入门到入土
文章目录
【全篇】C语言从入门到入土第一章 前言如何去学习,学习方法论
第二章 初识1.代码编译工具2.c程序的基础框架3.数据的表现形式变量1.要先定义后使用(变量名的定义是由自己决定的,一般倾向于顾文生义)2.那么如何命名变量名,以及命名规则3.数据类型3.1整型数3.2字符型,,ASCII码3.3浮点类型(小数)3.4 变量的存储类型1.static2.extern
4.强制转换常量1.整型常量介绍几个概念1.自加自减运算符2.三目运算符
4.输入输出4.1printf–打印4.2 scanf /扫描键盘
5.其他6.结语:编程案例——了解为主
第三章 流程控制1.if()…else… 条件语句,层层递进的关系运算符如何交换两个数的值?逻辑运算符if…else嵌套如果有三个数,如何让它从小到大排序,,,,,,,,要用到冒泡排序法,之后学习
2.switch( ) case… 并列,多分支语句2.1练习题
3.while循环控制语句3.1练习题
4.do…while5.for(){ }6.break,,,在还没有达到临界值的情况下,提前结束循环介绍一个重要概念,,取余7.用contine提前结束本次循环
循环嵌套嵌套练习题
第四章 数组4.1如何定义一个数组1.相同的数据类型,,,关于中括号[ ]的解释2.数组如何[遍历]3.初始化的方式4.数组的各种初始化方式以及數組的大小计算(代码)
4.2编程案例1.数组初始化及逆序输出2.斐波那契数列3.冒泡排序法,面试要考滴4.简单排序法,面试题
二维数组1.什么时候要用二维数组2.怎么样定义一个二维数组3.二维数组的初始化3.1.按行列的初始化3.2.没明确行列,类似一维数组3.3.部分赋初值
4.二维数组的遍历
第五章 函数1.为什么要用函数2.函数要先定义再使用,和变量一个道理定义:抓住三要素!牢牢记住!!!!!!!!!
3.函数的定义与调用1.定义无参数的函数2.定义有参数有返回值的函数3.定义空函数4.函数调用
4.形式参数和实际参数1.全局变量与局部变量
编程案例1.输入两个整数,要求输出最大值,用函数实现
调用过程函数调用的条件
5.函数的嵌套练习用函数嵌套来实现四个数里取得最大值
6.函数的递归(嵌套了自己)编程案例求阶乘
7.数组作为函数中的参数传递数组中的某个元素(意义不大)数组名当做函数实际参数关于数组作为函数参数的一些坑有意思的案例,关于地址(未来的指针)编程案例算出两个班级学生成绩的平均分(API)
8.二维数组作为函数的参数,他的形参怎么写?正确写法错误写法关心两点练习
9.全局变量编程案例
第六章 指针1.认识一下指针2.指针变量 = 存放地址的变量2.1如何定义一个指针变量以及如何使用指针变量2..2变量的访问方式2.3既然指针变量是存放别人地址的变量,那什么要区分类型呢
3.我们为什么要用指针3.1封装函数,实现两个数的交换3.2指针指向固定的区域
4.通过指针引用数组,重要面试4.1定义一个指针变量指向数组4.2指针增量和数组的关系4.3通过指针引用数组1.下标法2.指针法2.1偏移2.2取内容
数组名和指针的区别两种方法效率对比
编程案例1.函数封装数组初始化,遍历2.将数组中的n个元素按逆序存放,函数封装
5.指针与二维数组树立认知
小总结(嵌入式工程师笔试题会考)
6.数组指针例题
7.函数指针1.如何定义一个函数指针2.好用之处
8.指针数组1.定义,注意和数组指针的区别(面试会考)2.函数指针的使用
9.指针函数10.二级指针11.总结
第七章 字符串1.字符串的引入以及注意事项1.1字符数组,与数组差不多
2.字符串的内存存放方式及结束标志3.sizeof 与 strlen的区别4.动态开辟字符串(难点)5.几种字符串常用的API6.C语言实现断言函数assert7.字符串拼接strcat的使用及实现8.字符串比较函数strcmp使用及实现
第八章 结构体1.初识1.1为什么要用结构体1.2定义一个结构体1.3初始化一个结构体变量并引用1.4例题
2.结构体数组3.应用练习:选票系统4.结构体指针1.概念引入2.小应用1.指针在结构体数组中的偏移
5.共用体/联合体1.概念引入
6.宏定义define7.typedef
往期文章回顾:
【C语言从入门到入土】第一章前言
【C语言从入门到入土】第二章初识
【C语言从入门到入土】第三章流程控制
【C语言从入门到入土】第四章数组
【C语言从入门到入土】第五章函数
【C语言从入门到入土】第六章 指针(上)
【C语言从入门到入土】第六章 指针(下)
【C语言从入门到入土】第七章 字符串
【C语言从入门到入土】第八章 结构体
第一章 前言
如何去学习,学习方法论
1.看视频学习
不要拉进度条遇到熟悉的知识点或者二刷可以倍速看
2.视频中的代码
理解
照着打(形成关键词记忆,肌肉记忆,,,,很重要)
默写打
编译出错不要怕,有错误提示,,,要积累,,然后解决
不要丢掉没写对的代码,一定要调试正确为止,,,
错误代码也要积累,,,多多总结——(写博文,,CSDN,云笔记,QQ空间等等)
—————刚开始low没关系,目的是能编程能做东西
第二章 初识
1.代码编译工具
配置环境变量———-目的是命令终端中的任意文件夹能识别gcc指令
安装gcc工具———–mingw—下载地址(http://www.mingw-w64.org/doku.php)
博客参考:https://blog.csdn.net/Leo_LiangXuYuan/article/details/86763735
使用
打开命令终端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程序的基础框架
“最小组成”,写代码前先敲好
<code>#include <stdio.h>//编译预处理指令
int main()//程序的入口主函数main
{
/***你要写的代码*************
***********************/
return 0;//程序退出前返回给调用者(操作系统)的值
//程序(函数,功能)结束标志
}
3.数据的表现形式
变量
1.要先定义后使用(变量名的定义是由自己决定的,一般倾向于顾文生义)
int a = 3;
int b ;
b=a+1;
一个内存空间就像一个蜂巢快递柜一样,里面的快件会变,就像内存的数据会变一样,所以叫做变量
2.那么如何命名变量名,以及命名规则
1.由字母数字下划线组成,且只能以下划线或者字母开头,不能以数字开头
int data
int Mydata
int _mydata
int mydata
int 1data 错误
2.顾名思义,一看就可以知道是什么意思,这个要考验你的英语水平了哈哈哈哈!不会就写拼音吧,注意要区分大小写
3.驼峰命名法
int yiNianDuoShaoMiao
SecondsPerYear
second_Per_Year
_myMarkData
总之总之,比你直接int a;可强太多了
3.数据类型
计算机在内存中的存储方式是补码。
<code>原码:符号位加上真值的绝对值,用最高位(第一位)来表示符号位,其余表示数值
反码:正数的反码是其本身,负数的反码是在其符号位不变的前提下,其余按位取反
补码:正数的补码是其本身,负数的补码是在反码的基础上+1
3.1整型数
整数,,,int data = 10,
4个字节(一个字节8位,一共32位)c51(2) 65535 int a = 1000000; 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)
例子:当两个数相除不能够整除时,需要进行强制转换,来得到后面的小数
<code>/*****无强制转换********/
#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.整型常量
常量是指在程序运行期间其数值不发生变化的数据。整型常量通常简称为整数。
整数可以是十进制,也可以是八进制,十六进制。例如:
<code>十进制:15
八进制:21
在程序运行过程中,其值不能改变
符号常量 #define PI 3.14 (宏定义)
转义字符
介绍几个概念
C 语言自加 <code>++ / 自减 --
运算符实际就是对变量本身做 +1
或者 -1
操作
1.自加自减运算符
(自加自减运算符均为单目运算)
1.只需要一个运算量。若运算符位于运算对象前面时,称为前缀运算,如++a和 - -a;
2.而运算符位于运算对象后面时,称为后缀运算符,如a++和a- -,自加和自减运算符的功能是将运算对象加1或减1后,再将结果保存到运算对象中。
前缀和后缀运算分别等价的运算如下:
前缀:a=2,b=3,b=++a;等价于a=a+1;b=a运算结果:a=3,b=3;
后缀:a=2,b=3,b=a++;等价于b=a,a=a+1运算结果:a=3,b=2;
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… 条件语句,层层递进的
<code>if(条件){
表达式01
}else{
表达式02
};
关系运算符
如何交换两个数的值?
不交换土办法
逻辑运算符
if…else嵌套
<code>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… 并列,多分支语句
可以是字符,也可以是数字,,,直接看代码学习怎么用
<code>switch(输入的条件){
case 1:
表达式01;
break; //必须要加上这个语句才能结束
case 2:
表达式02;
break;
case 3;
表达式03;
break;
default:
条件都不符合;//在不满足上述所有情况时使用
}
2.1练习题
<code>#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循环控制语句
<code>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练习题
<code>/*错误案例*/
#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
先做一次循环再判断,
<code>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提前结束本次循环
<code>#include<stdio.h>
int main(){
for(int a=100;a<=200;a++){ //注意要用分号,,因为每个表达式换成单个
/*首先要明确一点,能被3整除,则余数就是0*/
if(a%3 ==0){
continue;
}
printf("%d",a);
}
return 0;
}
运行结果
循环嵌套
<code>#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++);//为了研究一共有几个数
}
}
}
运行结果
数的个数等于,行列相乘,,
嵌套练习题
<code>#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.数组如何[遍历]
遍历
下标法访问结合循环控制语句数组的地址为连续的
<code>/****************************
*****第一步;先给数组赋值******
**第二步;把数组的值给打印出来**
*****过程中用到的是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() 关键字,,可以计算括号里面数据的内存空间大小!!!!!注意他不是函数,面试可能会问
<code>#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.数组初始化及逆序输出
<code>#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.斐波那契数列
<code>#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;
}
报错了我滴宝,,,!!注意了
再来一次
<code>#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;
<code>/*从小到大排序*/
#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.定义无参数的函数
<code>#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.形式参数和实际参数
传递参数,传递的是值,,形参和实参值相同,但是地址空间不同
<code>#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.输入两个整数,要求输出最大值,用函数实现
<code>#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.函数的嵌套
一步步调用,一步步返回
练习
用函数嵌套来实现四个数里取得最大值
<code>#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.函数的递归(嵌套了自己)
一般在实际中很少用
编程案例
解题思路
<code>#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),他就变成了一个定值,就会出现下面的这种情况
求阶乘
<code>//我直接改上面的代码哈哈哈哈,,需要注意的就是数过大就会越界
#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.数组作为函数中的参数
传递数组中的某个元素(意义不大)
<code>//我要传递过来单个数组怎么弄?
#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;
}
数组名当做函数实际参数
<code>//我要是传递整个数组呢?
#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;
}
关于数组作为函数参数的一些坑
<code>//还是用的上面的代码,我们算一下数组的分别在实参和形参的大小
#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;
}
<code>//如果数组的个数变成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个字节
<code>//下面我们开始分析一下
//至于上面会出现一个警告我们不需要管,还没有学到指针
#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)
<code>#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 [ ] [ ]
<code>/*回顾一下之前的二维数组*/
#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;
}
然后你就会发现会爆错误
关心两点
数组数据类型二维中的一维数组有多少个
练习
练习:有3x4矩阵,初始化它并输出,然后求最大值并输出
<code>#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 个学生,封装一个函数,调用该函数后获得班上的平均分,最高分,最低分
<code>#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如何定义一个指针变量以及如何使用指针变量
* 的运算作用
<code>#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既然指针变量是存放别人地址的变量,那什么要区分类型呢
他的类型决定了,你指向(开辟)的空间大小,也决定了他的增量大小
<code>#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封装函数,实现两个数的交换
图一,传统我们交换数据的方式是,定义一个临时变量来承接,实现交换图二,而如果我们直接是封装一个函数来进行数据传递的话,封装的函数会另外开辟出来一个地址空间(在调用函数完毕这个空间会被释放),我们通过主函数把数值传递给封装的函数,在封装函数里面完成了数值的交换,但是主函数里面的数据并没有发生任何的变化。图三,我们通过指针直接把数据的地址传递过去,修改地址的数据,从而就可以改变主函数的值,这就是用指针的好处
回顾一下之前的
<code>//已经验证没有问题
#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 这个指针变量自己的一个地址
<code>#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(寄存器配置)
<code>//回顾一下
#include <stdio.h>
int main()
{
int a =10;
printf("address of a is 0x%p\n ",&a);
return 0;
}
<code>#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等于数组名:指向数组起始位置
<code>#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指针增量和数组的关系
我们知道数组在一个内存中是一个连续的地址,那么指针的增量与数组之间又存在着什么关系呢?
<code>#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循环
<code>#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通过指针引用数组
<code>#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取内容
指针当作数组名,下标法访问数组名拿来加
<code>///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#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++可行否??
<code>///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#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的使用
<code>#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.函数封装数组初始化,遍历
练习函数数组指针结合
<code>//首先来回顾一下我们以前写的代码
#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不动即可,,直接做一个临时变量,茶杯法直接交换即可
如果是奇数的话
那如果是偶数呢?结果同样成立
<code>#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],不要乱
<code>//一定要记住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个字节
树立认知
<code>//一定要记住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.数组指针
数组指针,一个指向数组的指针
指针数组,一堆指针组成的一个数组
<code>#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;
}
例题
输出二维数组任意行列的数
<code>#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 *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接口)
练习题
<code>#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.定义,注意和数组指针的区别(面试会考)
简单来说就是,一个由指针组成的数组,
<code>//数组指针的使用
#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.指针函数
一个返回值是指针的函数
概念
练习题:
<code>#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;
}
差别就是保存的是指针变量的地址。当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样
<code>//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字符数组,与数组差不多
注意单个字符用单引号‘ ’ 字符串要用双引号“ ”
<code>#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.动态开辟字符串(难点)
<code>#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,"helloworlld153123123");
int len = strlen("helloworlld153123123");
int newlen = len - 12 + 1;//减去原本的,再加上一个\0
realloc(p,newlen);
//四、清空函数
memset(p,'\0',12);//将上面新开辟的12个内存空间来赋为\0
return 0;
}
指针指向的字符串,呈现时只能显示一个
5.几种字符串常用的API
<code>#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的使用及实现
可以参考博客
<code>#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;
}
<code>//自己实现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'
********************************************************************/
<code>//自己实现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初始化一个结构体变量并引用
<code>//初始化结构体
#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
<code>#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.指针在结构体数组中的偏移
<code>
#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;
}
<code>#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需要加分号
<code>#include <stdio.h>
struct student{ char name;int sex} stu1
int main()
{
return 0;
}
上一篇: 用Python写一个烟花代码,程序员表白神器,附完整源码
下一篇: 2024 Visual Studio Code配置Python环境(保姆级)_visual studio 2024 python
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。