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

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

文章目录

往期文章回顾:
【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

使用

  1. 打开命令终端cmd

  2. cd指令跳到代码文件夹

  3. 编译和运行,-o选项,指定生成的程序名字

  4. gcc test.c -o pro
    指令 c文件 生成选项 新程序名字(a.exe)

  5. gcc test.c -g 让你的程序变成可调试(不需要了解那么深了,一般在程序崩的莫名其妙,不知道哪里出了问题可以试一试)

  6. gdb a.exe 之后输入 r 进入待运行状态(之后再运行就可以看到是哪里出现了问题了)

  7. 退出的话输入 q(quit),然后y(yes确认)

    ipconfig(打开局域网配置)

2.c程序的基础框架

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

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

3.数据的表现形式

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

请添加图片描述

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

在这里插入图片描述

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

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

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

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

在这里插入图片描述

3.驼峰命名法

int secondsPerYear
int yiNianDuoShaoMiao
SecondsPerYear
second_Per_Year
_myMarkData

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

3.数据类型

在这里插入图片描述

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

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

在这里插入图片描述

3.1整型数

整数,,,int data = 10,

4个字节(一个字节8位,一共32位)c51(2) 65535 int a = 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是自动换行的

在这里插入图片描述

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

在这里插入图片描述

调用过程
  1. 内存空间在这里插入图片描述

  2. 值传递在这里插入图片描述

  3. 值返回

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

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

    在这里插入图片描述

函数调用的条件
  1. 函数已被定义

  2. 调用库函数在这里插入图片描述

  3. 函数的声明在这里插入图片描述

在这里插入图片描述

5.函数的嵌套

一步步调用,一步步返回

在这里插入图片描述

练习
用函数嵌套来实现四个数里取得最大值
#include <stdio.h>

int getfortwo(int a,int b)
{
	return a>b?a:b;
	
}
int get_four_fordata(int a,int b,int c,int d)
{
	int max;
	max = getfortwo(a,b);
	max = getfortwo(max,c);
	max = getfortwo(max,d);
	return max;
}


int main()
{
	int a,b,c,d,best;
	puts("请输入四个数");
	scanf("%d%d%d%d",&a,&b,&c,&d);
	getchar();
	best = get_four_fordata(a,b,c,d);
	printf("你输入的四个数分别是%d,%d,%d,%d,最大的是%d\n",a,b,c,d,best);
	
	return 0;
}

在这里插入图片描述

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

一般在实际中很少用

编程案例

在这里插入图片描述

解题思路

在这里插入图片描述

#include <stdio.h>
int get(int a)
{
	int age;
	//那么通过if语句,如果你想得到的不是1,那么他会一直在调自己,直到=1之后,输出age的值
	//假如a=2;age = get(1)+2;,,然后这个get(1)就会调自己,get(1)=10,这样就可以得出结果了
	if(a == 1)
	{
		age = 10;
	}else{
		age = get(a-1)+2;//这里我就有一个问题,什么时候才可以让它停下??
	}
	 
	return age;
}


int main()
{
	int age,num;
	printf("请输入你想知道第几个学生的年龄\n");
	scanf("%d",&num);
	age = get(num);
	printf("第%d的年龄为%d",num,age);
	
	return 0;
}

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

在这里插入图片描述

求阶乘

在这里插入图片描述

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


int main()
{
	int b,num;
	printf("请输入你想知道几的阶乘\n");
	scanf("%d",&num);
	b = get(num);
	printf("%d的阶乘为%d",num,b);
	
	return 0;
}

在这里插入图片描述

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

传递数组中的某个元素(意义不大)
//我要传递过来单个数组怎么弄?
#include <stdio.h>
void printfdata(int data)//注意这里是一个普通的整型数,,而不是数组
{
	printf("%d\n",data);
	
}

int main()
{
	int array[] = {5,6,4,2,9,7};//这个拓展一下,就是为了看看,不定义数组大小会不会报错,结果是不会
	for(int a = 0;a<6;a++)
	{
		if(a == 5)
		{
			printf("\n");
		}
		printf("%d  ",array[a]);
		
	}
    int arr[3] = {1,2,3};
    printfdata(arr[2]);//我们把第三个数给传过去
	
    return 0;
}

在这里插入图片描述

数组名当做函数实际参数
//我要是传递整个数组呢?
#include <stdio.h>

	void printf_arr(int arr[3])//把这三个数组定义出来
	{
		for(int a=0;a<3;a++){
			printf("%d\n",arr[a]);
		}
		
	}
int main()
{
    int arr[3] = {1,2,3};
    printf_arr(arr);//这样会直接把整个数组给传递过去
	
    return 0;
}

在这里插入图片描述

关于数组作为函数参数的一些坑
//还是用的上面的代码,我们算一下数组的分别在实参和形参的大小
#include <stdio.h>

	void printf_arr(int arr[3])//把这三个数组定义出来
	{
		for(int a=0;a<3;a++){
			printf("%d\n",arr[a]);
		}
		printf("arr里面的array%d\n",sizeof(arr));//注意计算大小时是arr数组名,而不是数组元素
	}
int main()
{
    int arr[3] = {1,2,3};
    printf_arr(arr);//这样会直接把整个数组给传递过去
	printf("main里面的array%d\n",sizeof(arr));//注意sizeof是关键字而不是函数
    return 0;
}

在这里插入图片描述

//如果数组的个数变成10个呢?
#include <stdio.h>

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

在这里插入图片描述

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

//下面我们开始分析一下
//至于上面会出现一个警告我们不需要管,还没有学到指针
#include <stdio.h>

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

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

在这里插入图片描述

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

在这里插入图片描述

编程案例
算出两个班级学生成绩的平均分(API)
#include <stdio.h>

void initarray(int array[],int len)
{
    for(int a=0;a<len;a++)
    {
        printf("请输入第%d个学生的分数\n",a+1);
        scanf("%d",&array[a]);
    }
}

void printfarray(int array[],int len)
{
    printf("总共有%d个学生\n",len);
    for(int a=0;a<len;a++)
    {
        printf("他们的成绩依次为%d\n",array[a]);
    }    
}

float average(int array[],int len)
{
    int sum = 0;  
    float aver = 0.0;
    for(int a=0;a<len;a++)
    {
        sum+=array[a];
    }           
    aver = (float)sum/len;
    return aver;
}

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

在这里插入图片描述

在这里插入图片描述

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

正确写法

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

错误写法

int [ ] [ ]

在这里插入图片描述

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

然后你就会发现会爆错误

在这里插入图片描述

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

在这里插入图片描述

练习

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

在这里插入图片描述

回顾一下之前的

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

函数封装错误的方式

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

换了个寂寞哈哈哈哈哈

在这里插入图片描述

正确的方式

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

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

在这里插入图片描述

3.2指针指向固定的区域

单片机 armbootloader(寄存器配置)

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

在这里插入图片描述

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

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

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

01指向数组首元素的地址

在这里插入图片描述

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

1.下标法

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

2.指针法
2.1偏移

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

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

在这里插入图片描述

数组名和指针的区别

1.arr++可行否??

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

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

在这里插入图片描述

2.sizeof的使用

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

在这里插入图片描述

两种方法效率对比

在这里插入图片描述

编程案例

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

练习函数数组指针结合

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

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

如果是奇数的话

在这里插入图片描述

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

在这里插入图片描述

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

5.指针与二维数组

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

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

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

树立认知

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

6.数组指针

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

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

在这里插入图片描述

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

在这里插入图片描述

例题

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

#include <stdio.h>

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

    return 0;
}

7.函数指针

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

在这里插入图片描述

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

跟普通变量一样,

int a;
int *p;

char c;
char *p;

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

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

练习题

在这里插入图片描述

#include <stdio.h>
int getMax(int a,int b)
{
    return a>b?a:b;
}
int getMin(int a,int b)
{
    return a<b?a:b;
}
int getSum(int a,int b)
{
    return a+b;
}

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

回调函数的底层逻辑,

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

8.指针数组

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

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

在这里插入图片描述

//数组指针的使用
#include <stdio.h>
int main()
{
    int a,b,c,d;
    a = 10;
    b = 20;
    c = 30;
    d = 40;
    int *p[4] = {&a,&b,&c,&d};
    for(int i = 0;i<4;i++)
    {
     printf("%d\n",*p[i]); 
    }
    return 0;
}
2.函数指针的使用
//函数指针数组
#include <stdio.h>
int getMax(int a,int b)
{
    return a>b?a:b;
}
int getMin(int a,int b)
{
    return a<b?a:b;
}
int getSum(int a,int b)
{
    return a+b;
}

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

9.指针函数

一个返回值是指针的函数

概念

在这里插入图片描述

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

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

10.二级指针

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

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

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

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

在这里插入图片描述

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

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

在这里插入图片描述

11.总结

中小公司大概率考题

在这里插入图片描述

第七章 字符串

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

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

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

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

#include <stdio.h>
int main()
{
    char c = 'h';//定义的一个单字符,,这是以前的写法

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

//1.这个是字符串变量,数值可以修改   
char cdata2[] = "hello"; 	          //2.对上面进行改进的第二个方法
cdata2[3] = 'm';	//这个会修改成功

//2.字符串常量,不允许被修改
char *pchar   = "hello"; 	          //3.用指针的方法来定义字符串
*pchar = 'p';		//不会报错,但是会卡在这里

野指针

char *p;		//野指针,并没有明确的内存指向,很危险
*p = 'a';		//会报错

//注意指针的操作
1.保存地址可以,修改指向,指向字符串常量的地址空间
2.对野指针的内存空间操作不行

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

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

3.sizeof 与 strlen的区别

//1.sizeof计算的是整个的长度   2.strlen计算的是字符中的有效长度,记住是有效长度

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

在这里插入图片描述

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

#include <stdio.h>
#include <string.h>
#include <stdilb.h>

int main()
{
/********************************************************
    char *p;		//野指针,并没有明确的内存指向,很危险
	*p = 'a';		//会报错
*********************************************************/
//一、那么如何解决这种情况呢???
    //1.我们引入第一个C库函数malloc,,   2.函数原型  void *malloc(size_t size) 
    //3. C库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
    char *p;
    p = (char *)malloc(1);  //将malloc强转成char型,然后开辟出1个字节的内存空间,给到P
	*p = 'a';		
    puts(p);//这个时候我们就可以访问出来p了
	
    free(p);
    //一般我们在清空指针内的内存空间之后要把P = NULL;
/****************************************************************
  首先函数是存放在栈里面的,在调用完成之后会自动的释放内存
  但是malloc函数是存放在堆里面的,这样就会存在一个风险,如果他存在一个while循环里面,就会把内存耗光
  上面在malloc(1)之后,又有开辟出12个字节空间malloc(12),这个时候原先的malloc(1)就变成了悬挂指针(野指针的一种)
  这个时候我们就需要释放出来原本不需要的内存空间,就用到了free函数。
 
1. C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
 2.释放,防止内存泄露
  3.防止悬挂指针(野指针的一种)
*****************************************************************/
    
//二、那么如果我想再一次给他开辟一些空间存储另外一些数据呢?
     p = (char *)malloc(12);  //又开辟了12个空间
    strcpy(p,"helloworlld");  //这个函数第一个是放到哪里?第二个是放进什么?
    
//三、扩容函数
/*****************************************************************
函数原型 void *realloc(void *ptr, size_t size),,,扩容,,
C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 
所分配的 ptr 所指向的内存块的大小。
******************************************************************/
    //我们又放进很多字符,而这时候已经越界了,超出12个了,怎么办?
    p = (char *)malloc(12);
    strcpy(p,"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. 回忆:指针就是地址 指针变量就是存放地址的变量
    结构体也是变量
    变量访问有两种方式 : 1.变量名 2.地址
    之前案例,是用变量名访问
  2. 通过结构体变量地址来访问该结构体
    需要一个变量来保持这个地址:
    这和之前说的指针,其实是一样的
    只是指针类型是结构体
  3. int a; struct Test t;
    int *p; struct Test *p;
    p = &a; p = &t
#include <stdio.h>
#include <string.h>
struct student{
    char name;
    int  numble;
};
int main()
{
    //我们对照以前的
    int a;
    int *p =&a;
    
    char c;
    char *p2=&c;
    
    struct student stu1 ={'c',21};
    struct student *stu = &stu1; //结构体指针的定义
    
    //那我如果想要访问里面的数据呢?
    printf("%d\n",stu1.numble);//普通的变量名访问
    printf("%C\n",stu1.name);  //用最常见的.运算符
    stu ->name ='d';
    printf("%c\n",stu->name[32]);  //间接的地址访问
    printf("%d\n",stu->numble);//用->运算符
}

在这里插入图片描述

2.小应用
1.指针在结构体数组中的偏移

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

struct xuanmin{
    int tickets;
    char name[32];
};
int main()
{
    int total = 5;
    char person[32];
    int mark;
    int qipiao = 0;

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

5.共用体/联合体

1.概念引入
  1. 有时候同一个内存空间存放类型不同,不同类型的变量共享一块空间
  2. 结构体元素有各自单独空间
    共用体元素共享空间,空间大小有最大类型确定
  3. 结构体元素互不影响
    共用体赋值会导致覆盖
#include <stdio.h>
#include <string.h>

//1.联合体的大小取决于最大的整数数
//2.联合体里面的变量值都是在同一个地址里面存放着
struct Test{
    int    idata;
    char   cdata;
    double ddata;
};
union Testu{
    int    idata;
    char   cdata;
    double ddata;
};
int main()
{
    struct Test t;
    union  Testu u;
    
    printf("结构体的大小为%d\n",sizeof(t));
    printf("联合体的大小为%d\n",sizeof(u));
    
    printf("idata:%p\n",&t.idata);
    printf("cdata:%p\n",&t.cdata);
    printf("ddata:%p\n",&t.ddata);
    
    printf("idata:%p\n",&u.idata);
    printf("cdata:%p\n",&u.cdata);
    printf("ddata:%p\n",&u.ddata);
   return 0;
}

在这里插入图片描述

#include <stdio.h>
#include <string.h>

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

在这里插入图片描述

6.宏定义define

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

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

在这里插入图片描述

7.typedef

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

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

struct student{char name;int sex} stu1
int main()
{
    
    
    return 0;
}

相关推荐

  1. C语言入门入土】第一章前言

    2024-06-17 07:46:03       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-17 07:46:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-17 07:46:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-17 07:46:03       82 阅读
  4. Python语言-面向对象

    2024-06-17 07:46:03       91 阅读

热门阅读

  1. python 异常处理、随机数、

    2024-06-17 07:46:03       31 阅读
  2. AI学习指南机器学习篇-KNN算法实现

    2024-06-17 07:46:03       30 阅读
  3. linux 搭建一台自己的DNS服务器

    2024-06-17 07:46:03       34 阅读
  4. [AIGC] 选择LeetCode刷题的编程语言

    2024-06-17 07:46:03       34 阅读
  5. 比特币通用API服务

    2024-06-17 07:46:03       27 阅读
  6. Flink Watermark详解

    2024-06-17 07:46:03       26 阅读
  7. 矩阵补全IGMC 学习笔记

    2024-06-17 07:46:03       23 阅读
  8. ComfyUI

    ComfyUI

    2024-06-17 07:46:03      24 阅读
  9. 外键的基本概念

    2024-06-17 07:46:03       25 阅读
  10. C++多态

    2024-06-17 07:46:03       27 阅读