【全篇】C语言从入门到入土
文章目录
- 【全篇】C语言从入门到入土
- 第一章 前言
- 如何去学习,学习方法论
- 第二章 初识
- 1.代码编译工具
- 2.c程序的基础框架
- 3.数据的表现形式
- 变量
- 1.要先定义后使用(变量名的定义是由自己决定的,一般倾向于顾文生义)
- 2.那么如何命名变量名,以及命名规则
- 3.数据类型
- 3.1整型数
- 3.2字符型,,ASCII码
- 3.3浮点类型(小数)
- 3.4 变量的存储类型
- 1.static
- 2.extern
- 4.强制转换
- 常量
- 1.整型常量
- 介绍几个概念
- 1.自加自减运算符
- 2.三目运算符
- 4.输入输出
- 4.1printf–打印
- 4.2 scanf /扫描键盘
- 5.其他
- 6.结语:编程案例——了解为主
- 第三章 流程控制
- 第四章 数组
- 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.几种字符串常用的API
- 6.C语言实现断言函数assert
- 7.字符串拼接strcat的使用及实现
- 8.字符串比较函数strcmp使用及实现
- 第八章 结构体
- 1.初识
- 1.1为什么要用结构体
- 1.2定义一个结构体
- 1.3初始化一个结构体变量并引用
- 1.4例题
- 2.结构体数组
- 3.应用练习:选票系统
- 4.结构体指针
- 1.概念引入
- 2.小应用
- 1.指针在结构体数组中的偏移
- 5.共用体/联合体
- 1.概念引入
- 6.宏定义define
- 7.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程序的基础框架
“最小组成”,写代码前先敲好
#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 = 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)
例子:当两个数相除不能够整除时,需要进行强制转换,来得到后面的小数
/*****无强制转换********/
#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=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… 条件语句,层层递进的
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;
}
然后你就会发现会爆错误
关心两点
- 数组数据类型
- 二维中的一维数组有多少个
练习
练习:有3x4矩阵,初始化它并输出,然后求最大值并输出
#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,"helloworlld153123123");
int len = strlen("helloworlld153123123");
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;
}