嵌入式课程笔记_03_STDC03_20190310

文章目录

03_STDC03

day1: 字符串02 、 预处理指令01

STDC03_day01_01-字符串03.ts

不可以在程序中使用操作符操作字符串,应该使用一组标准函数
为了使用这组标准函数需要包含string.h头文件
strlen 统计字符串中有效字符的个数
​ 和sizeof关键字的结果不同

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10

strcat 用来合并两个字符串内容,补充:将字符串追加到字符数组后面
​ 这个函数有可能修改不属于数组的存储区,
​ 这有可能造成严重后果

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));	//补充:函数的返回值代表合并后的字符串
    printf("%s\n",str);	//补充:追加的字符串过长会修改字符数组后面的存储区,造成问题
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef

strncat 功能和strcat一样,但是可以避免出问题

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));	//补充:函数的返回值代表合并后的字符串
    printf("%s\n",str);	//补充:追加的字符串过长会修改字符数组后面的存储区,造成问题
	printf("%s\n",strncat(str,"xyzpqr",3));	//追加3个字符
	printf("%s\n",str);
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz

strcmp 用来比较两个字符串的大小
​ 根据字符ASCII码大小进行比较
​ 返回值为1表示前一个字符串大,
​ 返回值为-1表示后一个字符串大,
​ 返回值为0表示一样大

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));	//补充:函数的返回值代表合并后的字符串
    printf("%s\n",str);	//补充:追加的字符串过长会修改字符数组后面的存储区,造成问题
	printf("%s\n",strncat(str,"xyzpqr",3));	//追加3个字符
	printf("%s\n",str);
    printf("比较结果是%d\n",strcmp("abc","abd"));   
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1

STDC03_day01_02-字符串04.ts

strncmp 用来比较两个字符串的前n个字符

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));
    printf("%s\n",str);   
    printf("%s\n",strncat(str,"xyzpqr",3));
    printf("%s\n",str);
    printf("比较结果是%d\n",strcmp("abc","abd")); 
    printf("比较结果是%d\n",strncmp("abc","abd",2));
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1
比较结果是0  

strcpy 用来把一个字符串的内容复制到字符数组里
​ 如果数组里的存储区不能容纳字符串的内容,就会修改不属于数组的存储区,这样可能会有严重后果

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));
    printf("%s\n",str);   
    printf("%s\n",strncat(str,"xyzpqr",3));
    printf("%s\n",str);
    printf("比较结果是%d\n",strcmp("abc","abd")); 
    printf("比较结果是%d\n",strncmp("abc","abd",2));
    printf("%s\n",strcpy(str,"xyz"));
    printf("%s\n",str);
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1
比较结果是0    
xyz
xyz

strncpy 功能和strcpy一样,但是可以避免出问题

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));
    printf("%s\n",str);   
	printf("%s\n",strncat(str,"xyzpqr",3));
	printf("%s\n",str);
	printf("比较结果是%d\n",strcmp("abc","abd"));	
	printf("比较结果是%d\n",strncmp("abc","abd",2));
	printf("%s\n",strcpy(str,"xyz"));
	printf("%s\n",str);
	printf("%s\n",strncpy(str,"abc",2));
	printf("%s\n",str);
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1
比较结果是0    
xyz
xyz
abz
abz

memset 把字符数组里多个字符类型存储区的内容填充成同一个字符

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));
    printf("%s\n",str);   
	printf("%s\n",strncat(str,"xyzpqr",3));
	printf("%s\n",str);
	printf("比较结果是%d\n",strcmp("abc","abd"));	
	printf("比较结果是%d\n",strncmp("abc","abd",2));
	printf("%s\n",strcpy(str,"xyz"));
	printf("%s\n",str);
	printf("%s\n",strncpy(str,"abc",2));
	printf("%s\n",str);
	memset(str,'h',9);
	printf("%s\n",str);
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1
比较结果是0    
xyz
xyz
abz
abz
hhhhhhhhh

strstr 用来在一个字符串里查找另外一个字符串的位置
​ 如果找不到就返回NULL
​ 补充:找到返回字符串的地址,找不到返回NULL即:0,假

/CSD1702/biaoc/day11 01string.c

/*
    字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char str[10] = "abc";
    printf("有效字符个数是%d\n",strlen(str));
    printf("sizeof(str)是%d\n",sizeof(str)); 
    printf("%s\n",strcat(str,"def"));
    printf("%s\n",str);   
    printf("%s\n",strncat(str,"xyzpqr",3));
    printf("%s\n",str);
    printf("比较结果是%d\n",strcmp("abc","abd"));	
    printf("比较结果是%d\n",strncmp("abc","abd",2));
    printf("%s\n",strcpy(str,"xyz"));
    printf("%s\n",str);
    printf("%s\n",strncpy(str,"abc",2));
    printf("%s\n",str);
    memset(str,'h',9);
    printf("%s\n",str);
    printf("%s\n",strstr("abcdefghijk","def"));
    return 0;
}
结果:
有效字符个数是3
sizeof(str)是10
abcdef
abcdef
abcdefxyz
abcdefxyz
比较结果是-1
比较结果是0    
xyz
xyz
abz
abz
hhhhhhhhh
defghijk

STDC03_day01_03-字符串05.ts

以下两个字符串相关函数不需要包含string.h头文件
sprintf 按照格式把多个数字合并到一个字符数组里形成字符串

/CSD1702/biaoc/day11 02string.c

/*
	字符串函数演示	
*/
#include <stdio.h>
int main(){
    char str[20] = {0};
    printf("%d %c %g\n",34,'t',5.6f);
    sprintf(str,"%d %c %g\n",34,'t',5.6f);
    printf("%s",str);
    return 0;
}
结果:
34 t 5.6
34 t 5.6

sscanf 用来从字符串里获得多个数字并记录到变量里

/CSD1702/biaoc/day11 02string.c

/*
	字符串函数演示	
*/
#include <stdio.h>
int main(){
    char ch = 0;
    int num = 0;
    float fnum = 0.0f;
    char str[20] = {0};
    printf("%d %c %g\n",34,'t',5.6f);
    sprintf(str,"%d %c %g\n",34,'t',5.6f);
    printf("%s",str);
    //scanf("%c%d%g",&ch,&num,&fnum);
    sscanf("g 13 7.4","%c%d%g",&ch,&num,&fnum);
    printf("%g %c %d\n",fnum,ch,num);
    return 0;
}
结果:
34 t 5.6
34 t 5.6
7.4 g 13

以下两个函数可以把字符串里的数字转换成数字类型
以下两个函数使用的时候需要包含stdlib.h头文件
atoi 可以把字符串里开头的整数部分转换成整数类型

/CSD1702/biaoc/day11 03string.c

/*
	字符串演示
*/
#include <stdio.h>
#include <stdlib.h>
int main(){
    int num = atoi("34ds8g");
    printf("num是%d\n",num);
    return 0;
}
结果:
num是34

atof 可以字符串里开头的浮点数转换成双精度浮点类型

/CSD1702/biaoc/day11 03string.c

/*
	字符串演示
*/
#include <stdio.h>
#include <stdlib.h>
int main(){
	double dnum = 0.0;
    int num = atoi("34ds8g");
    printf("num是%d\n",num);
    dnum = atof("34.6dsgvds2dsf");
    printf("dnum是%lg\n",dnum);
    return 0;
}
结果:
num是34
dnum是34.6

练习:
编写程序从键盘得到多个考试成绩(考试成绩的个数不超过10个),把所有的考试成绩按照下面的格式合并成一个字符串
10,20,30,40,50
最后把合并的结果显示在屏幕上

STDC03_day01_04-字符串06.ts

/CSD1702/biaoc/day11 04string.c

/*
	字符串练习
*/
#include <stdio.h>
#include <string.h>
int main(){
    char buf[50] = {0},tmp[10] = {0};	//补充:每个考试成绩最多100,算上后面的逗号,10门考试成绩最多占用40字符
    int grade = 0;
    while(1){
        printf("请输入一个考试成绩:");
        scanf("%d",&grade);
        if (grade < 0){		//补充:输入负值结束循环
            break;
        }
        sprintf(tmp,"%d,",grade);	//补充:将一个考试成绩转换成字符串放在数组中
        strcat(buf,tmp);	//补充:将tmp数组中的内容合并到buf中,buf够长,不考虑溢出情况
    }
    buf[strlen(buf) - 1] = 0;		//补充:把最后一个“,”所在的存储区内容变成0,即'\0'
    printf("结果是%s\n",buf);
    return 0;
}
结果:
请输入一个考试成绩:10
请输入一个考试成绩:20
请输入一个考试成绩:30
请输入一个考试成绩:40
请输入一个考试成绩:50
请输入一个考试成绩:-5
结果是10,20,30,40,50

可以在scanf标准函数里使用%s作为占位符把用户输入的字符串记录到数组里
采用这种方法会造成严重后果

/CSD1702/biaoc/day11 05string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    char buf[10] = {0};
    printf("请输入一个字符串:");
    scanf("%s",buf);
    printf("%s\n",buf);
    return 0;
}
结果:
请输入一个字符串:asdg
asdg

请输入一个字符串:abc def
abc

输入字符超过存储区长度程序会崩溃

fgets函数也可以从键盘得到一个字符串并把内容记录到字符数组
这个函数可以避免scanf函数的问题
这个函数需要三个参数
1.数组名称
2.数组里存储区个数
3.用stdin代表键盘

/CSD1702/biaoc/day11 05string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    char buf[10] = {0};
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    printf("%s\n",buf);
    return 0;
}
结果:
请输入一个字符串:abc defxy
abc defxy

请输入一个字符串:sldjadfjlasdjf
sldjadfjl             //只会读取前面9个字符,最后一个字符存放'\0'
请输入一个字符串:abc
abc                 //如果输入的内容不能把数组充满就把最后输入的换行字符'\n'也放到数组里
					//显示的时候会多一个空行

如果输入的内容不能把数组充满就把最后输入的换行字符也放到数组里
如果输入内容过多就把多余的部分留在输入缓冲区里等下次读

/CSD1702/biaoc/day11 05string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    char buf[10] = {0};
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    printf("%s\n",buf);
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    printf("%s\n",buf);
    return 0;
}
结果:
请输入一个字符串:adjadfljaalsdjfajd
adjadflja
请输入一个字符串:alsdjfajd

STDC03_day01_05-字符串07.ts

/CSD1702/biaoc/day11 05string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    char buf[10] = {0};
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    scanf("%*[^\n]");	//清空输入缓冲区1
    scanf("%*c");		//清空输入缓冲区2
    printf("%s\n",buf);
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    printf("%s\n",buf);
    return 0;
}
结果:
请输入一个字符串:sadfadssadfadsfa
sadfadssa
请输入一个字符串:asdfasd
asdfasd


请输入一个字符串:adf	//补充:输入缓冲区没有多余语句时等待用户输入多余语句才能继续执行
ad					//补充:输入了多余的语句ad,清空了输入缓冲区,程序才会继续执行
adf

请输入一个字符串:adaad
adaad

输入缓冲区没有多余语句时等待用户输入多余语句才能继续执行

/CSD1702/biaoc/day11 05string.c

/*
	字符串演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    char buf[10] = {0};
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    if(strlen(buf) == 9 && buf[8] != '\n'){	//补充:如果输入缓冲区有多余语句
        scanf("%*[^\n]");	//清空输入缓冲区1
        scanf("%*c");		//清空输入缓冲区2        
    }
    printf("%s\n",buf);
    printf("请输入一个字符串:");
    //scanf("%s",buf);
    fgets(buf,10,stdin);
    printf("%s\n",buf);
    return 0;
}
结果:
请输入一个字符串:adsfaddasfadsf
adsfaddas
请输入一个字符串:adfad
adfad

请输入一个字符串:ada
ada

请输入一个字符串:asdadad
asdadad

每次使用fgets函数从键盘得到字符串以后都应该清理输入缓冲区里可能存在的多余数据,清理语句应该放在一个分支

练习:编写模拟登陆练习
让用户输入用户名和密码
程序检查用户名是不是admin,密码是不是123456
如果是就说明登录成功,否则登录失败
一共给用户三次机会
程序最后要给出登陆提示

/CSD1702/biaoc/day11 06string.c

/*
    模拟登录练习
*/
#include <stdio.h>
#include <string.h>
int main(){
    char buf[10] = {0};
    int num = 0;
    for (num = 1;num<= 3;num++){
        printf("请输入用户名:");
        fgets(buf,10,stdin);
        if(strlen(buf) == 9 && buf[8] != '\n'){ //清理可能存在的多余数据
            scanf("%*[^\n]");
            scanf("%*c");
        }
        if(strcmp(buf,"admin\n")){      //比较两个字符串用户名,相同返回0
            continue;                   //两个字符串不同,进入下次循环
        }
        printf("请输入密码:");
        fgets(buf,10,stdin);
        if(strlen(buf) == 9 && buf[8] != '\n'){ //清理可能存在的多余数据
            scanf("%*[^\n]");
            scanf("%*c");
        }
        if(strcmp(buf,"123456\n")){
            continue;
        }
        break;
    }
    if(num <= 3){
        printf("登录成功\n");
    }
    else {
        printf("登录失败\n");
    }
    return 0;
}

STDC03_day01_06-字符串08.ts

指针数组里的每个存储区是一个指针类型的存储区
字符指针数组里包含多个字符类型的指针,每个指针可以用来表示一个字符串
字符指针数组可以用来表示多个相关字符串

/CSD1702/biaoc/day11 07string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    //补充:字符指针数组,里面包含了多个字符类型的指针
    //补充:初始化,用多个地址进行初始化,使用字符串字面值初始化,编译时自动替换成5个地址
    char *strs[] = {"abc","def","pqr","vbn","xyz"};	
    int num = 0;
    for (num = 0;num <= 4;num++){
        printf("%s\n",strs[num]);
    }
    return 0;
}
结果:
abc
def
pqr
vbn
xyz

使用二维字符数组来记录多个相关字符串

/CSD1702/biaoc/day11 07string.c

/*
	字符串演示
*/
#include <stdio.h>
int main(){
    //补充:字符指针数组,里面包含了多个字符类型的指针
    //补充:初始化,用多个地址进行初始化,使用字符串字面值初始化,编译时自动替换成5个地址
    char *strs[] = {"abc","def","pqr","vbn","xyz"};
    //补充:二维数组分5组存储区,每组有10个存储区,只初始化前3个存储区
    char strs1[][10] = {"abc","def","pqr","vbn","xyz"};
    int num = 0;
    for (num = 0;num <= 4;num++){
        printf("%s\n",strs[num]);
        printf("%s\n",strs1[num]);	//补充:组下标,代表第一个存储区地址
    }
    return 0;
}

结果:
abc
abc
def
def
pqr
pqr
vbn
vbn
xyz
xyz

二维字符数组也可以用来表示多个相关字符串

字符指针数组比二维数组节省地方而且更灵活,所以一般采用字符指针数组

/CSD1702/biaoc/day11 08main.c

/*
	主函数形式参数演示
*/
#include <stdio.h>
//补充:argc表示argv[]字符指针数组有多少指针,argv[]字符指针数组
int main(int argc,char *argv[]){	
    int num = 0;
    for (num = 0;num <= argc - 1;num++){
        printf("%s\n",argv[num]);
    }
    return 0;
}
执行命令:
./a.out abc def xyz //将命令分隔成几段,每段做成一个字符串,所有字符串合并做成主函数第二个形参
结果:
./a.out
abc
def
xyz

命令行参数

练习:
编写程序解决鸡兔同笼问题
程序中从命令中得到头和脚的个数,然后计算出鸡和兔子各有多少只并把结果显示在屏幕上
./a.out 40 100

/CSD1702/biaoc/day11 09string.c

/*
	主函数参数练习
*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
    int heads = 0,legs = 0,num = 0;
    if(argc < 3){
        printf("命令错误\n");
        return 0;
    }
    heads = atoi(argv[1]);  //从字符串中将数字拿出来
    legs = atoi(argv[2]);   //从字符串中将数字拿出来
    for(num = 0;num <= heads;num++){
        if(4 * num + 2 * (heads - num) == legs){
            printf("兔子有%d只,鸡有%d只",num,heads - num);
        }
    }
    return 0;
}

结果:
./a.out 40 100
兔子有10只,鸡有30只

STDC03_day01_07-预处理指令01.ts

#define预处理指令可以用来定义

宏可以用来给数字起名字

/CSD1702/biaoc/day11 10macro.c

/*
    宏演示
*/
#include <stdio.h>
#define      PI        3.14f
int main(){	
    int radius = 0;
    printf("请输入半径:");
    scanf("%d",&radius);
    printf("周长是%g\n",2 * PI * radius);
    return 0;
}
结果:
请输入半径:5
周长是31.4

定义宏的时候应该把宏名称写在前面,把宏代表的数字写后面
宏名称通常全都是由大写字母构成
宏名称里不能包含空格
用宏给数字起名字的时候不能使用赋值操作符
编译器会把程序中的宏名称都替换成它所代表的数字

编译时采用-E命令,可以在终端中查看替换后的结果
gcc -E 10macro.c

/CSD1702/biaoc/day11 10macro.c

/*
    宏演示
*/
#include <stdio.h>
#define      PI        3.14f
int main(){	
    float pi = 3.14f;
    int radius = 0;
    printf("请输入半径:");
    scanf("%d",&radius);
    printf("周长是%g\n",2 * PI * radius);
    printf("周长是%g\n",2 * pi * radius);
    return 0;
}
结果:
请输入半径:5
周长是31.4
周长是31.4

可以在编译命令中使用-D选项决定宏名称代表的数字

STDC03_day01_08-预处理指令02.ts

/CSD1702/biaoc/day11 10macro.c

/*
    宏演示
*/
#include <stdio.h>
//#define      PI        3.14f
int main(){	
    float pi = 3.14f;
    int radius = 0;
    printf("请输入半径:");
    scanf("%d",&radius);
    printf("周长是%g\n",2 * PI * radius);
    printf("周长是%g\n",2 * pi * radius);
    return 0;
}
gcc -DPI=3.14f 10macro.c
结果:
请输入半径:5
周长是31.4
周长是31.4

如果程序中有些数字只有在编译的时候才能知道,就该用宏名称代表它们,然后在编译命令里使用-D选项指定这些数字

练习:
假设有一个如下格式的字符串
10,20,30,40,50
编写程序计算字符串里所有数字的和并把结果显示在屏幕上

/CSD1702/biaoc/day11 11string.c

/*
    字符串练习
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
    //p_grade字符指针,代表已经得到的字符串,p_tmp指针代表成绩的地址
    char *p_grade = "10,20,30,40,50",*p_tmp = p_grade;
    int sum = 0;
    while(1){
        sum += atoi(p_tmp);	//从p_tmp字符串得到数字
        p_tmp = strstr(p_tmp,",");	//在字符串中找',',返回','的地址
        if(!p_tmp){	//找不到','后,p_tmp为空,让循环结束
            break;
        }
        p_tmp++;//找到逗号后边数字的位置,p_tmp和后边的考试成绩捆绑
    }
}

预习:
​ 1.宏
​ 2.条件编译
​ 3.多文件编程

day2: 预处理指令02 、 构建大型程序

STDC03_day02_01-预处理指令03.ts

/CSD1702/biaoc/day12 01macro.c

/*
	宏演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
    int arr[SIZE] = {0};
    srand(time(0));
    for(num = 0;num <= SIZE - 1;num++){
        arr[num] = rand() % 36 + 1;
    }
    for(num = 0;num <= SIZE - 1;num++){
        printf("%d ",arr[num]);
    }
    printf("\n");
    return 0;
}
编译:gcc -DSIZE=5 01macro.c
结果:生成5个从1到36随机数

/CSD1702/biaoc/day12 01macro.c

/*
	宏演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
    int arr[SIZE] = {0};
    srand(time(0));
    for(num = 0;num <= SIZE - 1;num++){
        arr[num] = rand() % MAX +1;
    }
    for(num = 0;num <= SIZE - 1;num++){
        printf("%d ",arr[num]);
    }
    printf("\n");
    return 0;
}
编译:gcc -DSIZE=5 -DMAX=70 01macro.c
结果:生成5个随机数 从1到70

STDC03_day02_02-预处理指令04.ts

宏也可以用来给一个计算公式起名字
计算公式里包含未知数字,宏的参数用来代表这些数字

/CSD1702/biaoc/day12 02macro.c

/*
	宏演示
*/
#include <stdio.h>
#define    PI     3.14f
#define    CIRCLE(r)    2 * PI * r
int main(){
    int radius = 0;
    printf("请输入半径:");
    scanf("%d",&radius);
    printf("周长是%g\n",CIRCLE(radius));
    return 0;
}

结果:
请输入半径:5
周长是31.4
CIRCLE(r)
2 * PI * r
2 * 3.14f * radius

带参数的宏是采用二次替换的方式进行处理的

宏的参数不一定代表数字,所以它没有类型名称
如果宏有多个参数就需要在相邻参数名称之间用逗号分开

宏不能使用自己的存储区和函数进行数据传递
宏没有形式参数也没有用来记录返回值的存储区

/CSD1702/biaoc/day12 03macro.c

/*
	宏演示
*/
#include <stdio.h>
#define   ABS(n)   n >= 0 ? n : 0 - n
int abs(int num){
    if(num > 0){
        return 0;
    }
    else{
        return 0 - num;
    }
}
int main(){
    int num = 0;
    printf("请输入一个数字:");
    scanf("%d",&num);
    printf("绝对值是%d\n",abs(num));
    printf("绝对值是%d\n",ABS(num));
    return 0;
}

能当做数字使用的宏必须写成表达式(因为宏没有用来记录返回值的存储区)

STDC03_day02_03-预处理指令05.ts

/CSD1702/biaoc/day12 03macro.c

/*
	宏演示
*/
#include <stdio.h>
#define   ABS(n)   n >= 0 ? n : 0 - n
#define   NEG(n)   n = 0 - n
int abs(int num){
    if(num > 0){
        return 0;
    }
    else{
        return 0 - num;
    }
}
void neg(int *p_num){
    *p_num = 0 - *p_num;
}
int main(){
    int num = 0;
    printf("请输入一个数字:");
    scanf("%d",&num);
    printf("绝对值是%d\n",abs(num));
    printf("绝对值是%d\n",ABS(num));
    neg(&num);
    printf("num是%d\n",num);
    NEG(num);
    printf("num是%d\n",num);
    return 0;
}
结果:
请输入一个数字:6
绝对值是6
绝对值是6
num是-6
num是6

宏的参数直接代表了函数的存储区,对宏参数内容的修改就是对函数存储区内容的修改

STDC03_day02_04-预处理指令06.ts

练习:编写宏实现两个整数相减的功能

/CSD1702/biaoc/day12 04macro.c

/*
    宏演示
*/
#include <stdio.h>
#define   SUB(x,y)   x - y
int main(){
    printf("SUB(10,5)是%d\n",SUB(10,5));
    printf("20 - SUB(10,5)是%d\n",20 - SUB(10,5)); 
    return 0;
}
结果:
SUB(10,5)是5
20 - SUB(10,5)是5    //20 - 10 - 5

宏没有用来记录返回值的存储区,所以不能保证有限计算宏里面的操作符
能当做数字使用的宏必须写在一对小括号里面

/CSD1702/biaoc/day12 04macro.c

/*
    宏演示
*/
#include <stdio.h>
#define   SUB(x,y)   (x - y)
int main(){
    printf("SUB(10,5)是%d\n",SUB(10,5));
    printf("20 - SUB(10,5)是%d\n",20 - SUB(10,5)); 
    printf("SUB(20,10 - 5)是%d\n",SUB(20,10 - 5));
    return 0;
}
结果:
SUB(10,5)是5
20 - SUB(10,5)是15    //20 - (10 - 5)
SUB(20,10 - 5)是5     //(20 - 10 - 5)

宏没有形式参数,所以不能保证优先计算里面的操作符
宏的所有能当做数字使用的参数也应该写在小括号里

/CSD1702/biaoc/day12 04macro.c

/*
    宏演示
*/
#include <stdio.h>
#define   SUB(x,y)   ((x) - (y))
int main(){
    printf("SUB(10,5)是%d\n",SUB(10,5));
    printf("20 - SUB(10,5)是%d\n",20 - SUB(10,5)); 
    printf("SUB(20,10 - 5)是%d\n",SUB(20,10 - 5));
    return 0;
}
结果:
SUB(10,5)是5
20 - SUB(10,5)是15    //20 - ((10) - (5))
SUB(20,10 - 5)是15    //(20) - (((10) - (5)))

/CSD1702/biaoc/day12 05macro.c

/*
    宏演示
*/
#include <stdio.h>
#define   SQUARE(n)   ((n) * (n))
int main(){
    int num = 5;
    printf("SQUARE(num)是%d\n",SQUARE(num));
    printf("SQUARE(++num)是%d\n",SQUARE(++num));
    return 0;
}
结果:
SQUARE(num)是25
SQUARE(++num)是49    //不同的编译器可能出现不一样的结果

不要使用自增或自减的结果作为宏的参数使用

编写宏的时候可以使用一些特殊的符号,这些符号叫做宏操作符
#是一个宏操作符,它可以把宏的参数转换成字符串字面值

/CSD1702/biaoc/day12 06opr.c

/*
    宏操作符演示
*/
#include <stdio.h>
#define   STR(n)   #n
int main(){
    STR(2 + 4);
    return 0;
}

gcc -E 06opr.c

int main(){
    "2 + 4";
    return 0;
}

##也是一个宏操作符,它可以把一个代表标识符的参数和其他内容连接形成新的标识符

/CSD1702/biaoc/day12 06opr.c

/*
    宏操作符演示
*/
#include <stdio.h>
#define   STR(n)   #n
#define   PTR(n)   p_##n
int main(){
    int num = 0;
    //int *p_num = &num;
    int *PTR(num) = &num;
    STR(2 + 4);
    return 0;
}

gcc -E 06opr.c

int main(){
	int num = 0;
	
	int *p_num = &num;
    "2 + 4";
    return 0;
}

STDC03_day02_05-预处理指令07.ts

条件编译可以在编译的时候从几组语句中选择一组编译而忽略其他组
#ifdef/#ifndef...#else...#endif
以上结构可以根据一个宏名称是否被定义过从两种语句中选择一组编译
这个最开始的预处理指令需要从两个里面选择一个,不论选择哪个后面都需要跟着一个宏名称
如果选择#ifdef就表示他后面的宏名称被定义过的时候编译前一组语句,否则编译后一组语句
如果选择#ifndef刚好和#ifdef相反

/CSD1702/biaoc/day12 07compile.c

/*
	条件编译演示
*/
#include <stdio.h>
int main(){
#ifdef     YI
    printf("1\n");
#else    
    printf("2\n");
#endif
    return 0;
}
gcc 07compile.c
./a.out
2

gcc -DYI 07compile.c
./a.out
1

/CSD1702/biaoc/day12 07compile.c

/*
	条件编译演示
*/
#include <stdio.h>
int main(){
#ifndef     ER
    printf("1\n");
#else    
    printf("2\n");
#endif
    return 0;
}

gcc 07compile.c
./a.out
1

gcc -DER 07compile.c
./a.out
2

练习:
编写点菜程序,可以编译两个不同的版本,程序要能给出正确提示
1代表龙虾
2代表小鸡炖蘑菇

/CSD1702/biaoc/day12 08order.c

/*
    点菜练习
*/
#include <stdio.h>
int main(){
    int order = 0;
    printf("请点菜:");
    scanf("%d",&order);
#ifdef    		ZHAOBENSHAN
    if(order == 2){
        printf("真没有\n");
    }
    else{
        printf("没有\n");
    }
#else
    if(order == 2){
        printf("没有\n");
    }
    else{
        printf("有\n");
    }
#endif
    return 0;
}
gcc 08order.c
./a.out
请点菜:1
有
./a.out
请点菜:2
没有

gcc -DZHAOBENSHAN 08order.c
./a.out
请点菜:1
没有
./a.out
请点菜:2
真没有

局限性:1.只能从两组语句中选择一组来编译
2.必须根据一个宏名称是否被定义过来选择

STDC03_day02_06-预处理指令08.ts

#if...#elif(任意多次)...#else...#endif
以上结构也可以实现条件编译,它可以根据任意逻辑表达式从多组语句中选择一组编译
/CSD1702/biaoc/day12 09compile.c

/*
    条件编译演示
*/
#include <stdio.h>
int main(){
#if    defined(GONGCHAGN)        //工厂店,打八折
    printf("80%%\n");
#elif  !defined(GONGCHANG) && !defined(JINGPIN)   
    printf("100%%\n");
#else                //精品店,涨价20%
    printf("120%%\n");
#endif
    return 0;
}

STDC03_day02_07-构建大型程序01.ts

多文件编程的时候每个文件可以包含多个函数,一个函数只能属于一个文件

/CSD1702/biaoc/day12 10main.c

/*
    多文件编程演示
*/
#include <stdio.h>
int add(int num,int num1){
    return num + num1;
}
int main(){
    int num = 0,num1 = 0,sum = 0;
    printf("请输入两个数字:");
    scanf("%d%d",&num,&num1);
    sum = add(num,num1);
    printf("求和结果是%d\n",sum);
    return 0;
}
结果:
请输入两个数字:3 6
求和结果是9

多文件编程基本步骤:
1.把所有函数分散在多个不同的源文件里(主函数通常单独占用一个文件)
2.为每个源文件编写配对的以.h作为扩展名的头文件(主函数所在的文件不需要配对头文件)所有不分配内存的内容都可以写在头文件里头文件里至少要包含配对源文件里所有函数的声明
3.在每个源文件里使用#include预处理指令包含必要的头文件(配对头文件是必要头文件,如果源文件里使用了头文件里声明的函数则这个头文件也是必要头文件)

/CSD1702/biaoc/day12 10add.h

int add(int,int);

/CSD1702/biaoc/day12 10add.c

int add(int num,int num1){
    return num + num1;
}

/CSD1702/biaoc/day12 10main.c

/*
    多文件编程演示
*/
#include <stdio.h>
#include "10add.h"
int main(){
    int num = 0,num1 = 0,sum = 0;
    printf("请输入两个数字:");
    scanf("%d%d",&num,&num1);
    sum = add(num,num1);
    printf("求和结果是%d\n",sum);
    return 0;
}
gcc 10main.c 10add.c
./a.out
结果:
请输入两个数字:2 6
求和结果是8

在gcc命令后列出所有源文件名称就可以编译多个文件组成的程序

STDC03_day02_08-构建大型程序02.ts

使用make工具,可以把多文件程序的编译过程记录到Makefile文件里,然后使用make工具根据Makefile文件内容编译程序

/CSD1702/biaoc/day12 10add.h

#ifndef     __10ADD_H_
#define     __10ADD_H_
int add(int,int);
#endif    //__10ADD_H_

多文件编程的程序中,因为文件数量很多,经常会出现,一个源文件,通过不同的方式把同一个头文件包含了很多次。这会带来头文件被包含几次,程序就编译几次,会浪费编译时间。
头文件内容应该包含在一组预处理指令中,这样可以避免头文件内容被多次编译
第一个预处理指令必须是#ifndef

头文件里使用的宏名称应该根据文件路径变化得到

练习:
编写函数从键盘得到一个整数并把这个整数传递给调用函数
用多文件编程的方式实现

/CSD1702/biaoc/day12 11read.h

/*
    多文件编程练习
*/
#ifndef    __11READ_H__
#define    __11READ_H__ 
int read(void);
#endif   //__11READ_H__

/CSD1702/biaoc/day12 11read.c

/*
    多文件编程练习
*/
#include <stdio.h>
#include "11read.h"
int read(void){
    int num = 0;
    printf("请输入一个数字:");
    scanf("%d",&num);
    return num;
}

/CSD1702/biaoc/day12 11main.c

/*
    多文件编程练习
*/
#include <stdio.h>
#include "11read.h"
int main(){
    int num = read();
    printf("num是%d\n",num);
    return 0;
}

补充:源文件必须要包含它配对的头文件
使用gcc -c单独编译一个文件的时候,如果一个源文件包含它配对的头文件,头文件的函数声明和源文件的函数声明不一致,则会出错

STDC03_day02_09-构建大型程序03.ts

全局变量作用域包含程序中所有语句,

/CSD1702/biaoc/day12 12read.h

/*
    多文件编程练习
*/
#ifndef    __12READ_H__
#define    __12READ_H__ 
extern int num;
void read(void);
#endif   //__12READ_H__

/CSD1702/biaoc/day12 12read.c

/*
    多文件编程练习
*/
#include <stdio.h>
#include "12read.h"
//static int num;    错误
int num;    //声明一个全局变量
void read(void){
    printf("请输入一个数字:");
    scanf("%d",&num);
    return num;
}

/CSD1702/biaoc/day12 12main.c

/*
    多文件编程练习
*/
#include <stdio.h>
#include "12read.h"
int main(){
    read();
    printf("num是%d\n",num);
    return 0;
}

如果想从一个文件里使用另外一个文件里声明的全局变量就需要使用extern关键字再次声明那个变量

使用extern关键字声明变量的语句不会分配内存,所以通常放在头文件

不可以跨文件使用静态全局变量

练习:
编写宏实现英文字符大小写的转换

/CSD1702/biaoc/day12 13macro.c

/*
    宏练习
*/
#include <stdio.h>
#define    CASE(ch)   ((ch) >= 'a' && (ch) <= 'z' ? (ch) - 'a' +'A' : (ch) - 'A' + 'a')
int main(){
    char ch = 0;
    printf("请输入一个字符:");
    scanf("%c",&ch);
    printf("转换的结果是%c\n",CASE(ch));
    return 0;
}

预习:
​ 1.结构体

day3: 结构

STDC03_day03_01-结构01.ts

C语言里可以在一个存储区里记录多个数字
这种存储区的类型叫结构体类型,这种类型需要程序员编写语句创建出来
结构体类型存储区里可以包含多个子存储区,每个子存储区可以用来记录一个数字
结构体不同存储区的类型可以不同,子存储区的类型也可以是结构体类型
结构体声明语句可以用来创建结构体类型
声明结构体的时候需要使用struct关键字

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
struct person{
    int age;
    float height;
    char name[10];
};
int main(){
    struct person prsn;    //结构体变量声明   
    return 0;
}

结构体声明语句中包含多个变量声明语句,每个变量叫做结构体的成员变量,每个成员变量代表了结构体存储区的一个子存储区
成员变量声明语句不会分配存储区,它们只是用来表示子存储区的类型和名称
C语言里结构体不能包含函数
结构体声明语句不会分配内存,所以可以写在头文件里
可以把结构体作为类型声明变量,这种变量叫结构体变量
结构体变量被分配了存储区,它们可以用来记录数字
声明结构体变量的时候需要把struct关键字和结构体名称一起作为类型名称使用

STDC03_day03_02-结构02.ts

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;
int main(){
    struct person prsn;    //结构体变量声明   
    sperson prsn1;
    return 0;
}

typedef关键字可以用来给一个类型起别名
通常会给结构体类型起别名,这个别名就可以作为结构体类型名称使用

可以把声明结构体类型的语句和起别名的语句合并成一条语句

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct person{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    struct person prsn;    //结构体变量声明   
    sperson prsn1;
    return 0;
}

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1;
    return 0;
}

这个时候可以省略结构体本身的名称

STDC03_day03_03-结构03.ts

声明结构体变量的时候也应该进行初始化
可以像初始化数组一样初始化结构体变量

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1 = {20,1.65,"abc"};
    return 0;
}

结构体变量通常不会作为整体使用,一般一次只使用其中的某个子存储区
可以采用以下写法表示结构体里的某个子存储区
prsn.age
其中prsn是一个结构体变量名称,age是一个成员变量名称

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1 = {20,1.65,"abc"};
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    return 0;
}
结果:
年龄是20
身高是1.65
姓名是abc

编写程序从键盘得到姓名身高年龄并显示在屏幕上

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1 = {20,1.65,"abc"};
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    printf("请输入年龄:");
    scanf("%d",&(prsn1.age));
    printf("请输入身高:");
    scanf("%g",&(prsn1.height)); //用户输入身高后还留了一个换行字符,计算机将身高数据拿走后,换行字符留到输入缓冲区中
    printf("请输入姓名:");
    scanf("%*[^\n]");    //为了避免fgets拿到上一次输入留下的换行字符,需要清空缓冲区
	scanf("%*c"); 
    fgets(prsn1.name,10,stdin);
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    return 0;
}
结果:
年龄是20
身高是1.65
姓名是abc
请输入年龄:25
请输入身高:1.80
请输入姓名:def
年龄是25
身高是1.8
姓名是def

同类型的结构体变量之间是可以直接赋值

STDC03_day03_04-结构04.ts

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1 = {20,1.65,"abc"};
    sperson prsn2 = {0};
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    printf("请输入年龄:");
    scanf("%d",&(prsn1.age));
    printf("请输入身高:");
    scanf("%g",&(prsn1.height));  
    printf("请输入姓名:");
    scanf("%*[^\n]");
    scanf("%*c"); 
    fgets(prsn1.name,10,stdin);
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    prsn2 = prsn1;
    printf("年龄是%d\n",prsn2.age);
    printf("身高是%g\n",prsn2.height);
    printf("姓名是%s\n",prsn2.name);    
    return 0;
}
结果:
年龄是20
身高是1.65
姓名是abc
请输入年龄:25
请输入身高:1.80
请输入姓名:def
年龄是25
身高是1.8
姓名是def

年龄是25
身高是1.8
姓名是def

结构体指针可以和结构体存储区捆绑
结构体指针和结构体存储区捆绑后就可以采用以下写法表示子存储区
p_person->age
其中p_person是一个结构体指针,age是一个成员变量名称

/CSD1702/biaoc/day12 01struct.c

/*
    结构体演示
*/
#include <stdio.h>
/*struct person{
    int age;
    float height;
    char name[10];
};
typedef struct person sperson;*/
typedef struct /*person*/{
    int age;
    float height;
    char name[10];
} sperson;
int main(){
    //struct person prsn;    //结构体变量声明   
    sperson prsn1 = {20,1.65,"abc"};
    sperson prsn2 = {0};
    sperson *p_person = NULL;	//结构体指针变量
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    printf("请输入年龄:");
    scanf("%d",&(prsn1.age));
    printf("请输入身高:");
    scanf("%g",&(prsn1.height));  
    printf("请输入姓名:");
    scanf("%*[^\n]");
    scanf("%*c"); 
    fgets(prsn1.name,10,stdin);
    printf("年龄是%d\n",prsn1.age);
    printf("身高是%g\n",prsn1.height);
    printf("姓名是%s\n",prsn1.name);
    prsn2 = prsn1;			//同类型的结构体变量之间直接赋值
    printf("年龄是%d\n",prsn2.age);
    printf("身高是%g\n",prsn2.height);
    printf("姓名是%s\n",prsn2.name);  
    p_person = &prsn1;		//结构体指针和结构体存储区捆绑	
    printf("年龄是%d\n",p_person->age);
    printf("身高是%g\n",p_person->height);
    printf("姓名是%s\n",p_person->name); 
    return 0;
}
结果:
年龄是20
身高是1.65
姓名是abc
请输入年龄:12
请输入身高:1.23
请输入姓名:dsg
年龄是12
身高是1.23
姓名是dsg

年龄是12
身高是1.23
姓名是dsg

年龄是12
身高是1.23
姓名是dsg

STDC03_day03_05-结构05.ts

练习:
声明两个结构体,一个结构体用来记录一个像素的位置,另外一个结构体用来记录一个水平长方形的位置

/CSD1702/biaoc/day12 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;		//补充:用来记录像素位置
typedef struct{
    pt pt1,pt2;
} rect;		//补充:用来记录水平长方形位置
int main(){
    rect r = {0};
    printf("请输入一个水平长方形的位置:");
    scanf("%d%d%d%d",&(r.pt1.row),&(r.pt1.col),&(r.pt2.row),&(r.pt2.col));
    printf("位置是((%d,%d),(%d,%d))\n",r.pt1.row,r.pt1.col,r.pt2.row,r.pt2.col);
    return 0;
}
结果:
请输入一个水平长方形的位置:3 5 1 8
位置是((3,5),(1,8))

/CSD1702/biaoc/day12 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;
typedef struct{
    pt pt1,pt2;
} rect;
int main(){
    rect r = {0};
    rect *p_r = &r;		//补充:声明结构体指针变量,和结构体变量进行捆绑
    printf("请输入一个水平长方形的位置:");
    /*scanf("%d%d%d%d",&(r.pt1.row),&(r.pt1.col),&(r.pt2.row),&(r.pt2.col));
    printf("位置是((%d,%d),(%d,%d))\n",r.pt1.row,r.pt1.col,r.pt2.row,r.pt2.col);*/
    scanf("%d%d%d%d",&(p_r->pt1.row),&(p_r->pt1.col),&(p_r->pt2.row),&(p_r->pt2.col));
    printf("位置是((%d,%d),(%d,%d))\n",p_r->pt1.row,p_r->pt1.col,p_r->pt2.row,p_r->pt2.col);
    return 0;
}
结果:
请输入一个水平长方形的位置:3 5 8 7
位置是((3,5),(8,7))

STDC03_day03_06-结构06.ts

计算水平长方形面积,要求用结构体变量和结构体指针两种方法

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;
typedef struct{
    pt pt1,pt2;
} rect;
int main(){
	int area = 0;
    rect r = {0};
    rect *p_r = &r;
    printf("请输入一个水平长方形的位置:");
    scanf("%d%d%d%d",&(r.pt1.row),&(r.pt1.col),&(r.pt2.row),&(r.pt2.col));
    /*printf("位置是((%d,%d),(%d,%d))\n",r.pt1.row,r.pt1.col,r.pt2.row,r.pt2.col);*/
    /*scanf("%d%d%d%d",&(p_r->pt1.row),&(p_r->pt1.col),&(p_r->pt2.row),&(p_r->pt2.col));
    printf("位置是((%d,%d),(%d,%d))\n",p_r->pt1.row,p_r->pt1.col,p_r->pt2.row,p_r->pt2.col);*/
    area = (r.pt1.row - r.pt2.row) * (r.pt1.col - r.pt2.col); 
    area = area >= 0 ? area : 0 - area;	//补充:采用带赋值的三步表达式
    printf("面积是%d\n",area);
    return 0;
}
结果:
请输入一个水平长方形的位置:1 6 3 12
面积是12

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;
typedef struct{
    pt pt1,pt2;
} rect;
int main(){
	int area = 0;
    rect r = {0};
    rect *p_r = &r;
    printf("请输入一个水平长方形的位置:");
    scanf("%d%d%d%d",&(r.pt1.row),&(r.pt1.col),&(r.pt2.row),&(r.pt2.col));
    /*printf("位置是((%d,%d),(%d,%d))\n",r.pt1.row,r.pt1.col,r.pt2.row,r.pt2.col);*/
    /*scanf("%d%d%d%d",&(p_r->pt1.row),&(p_r->pt1.col),&(p_r->pt2.row),&(p_r->pt2.col));
    printf("位置是((%d,%d),(%d,%d))\n",p_r->pt1.row,p_r->pt1.col,p_r->pt2.row,p_r->pt2.col);*/
    //area = (r.pt1.row - r.pt2.row) * (r.pt1.col - r.pt2.col);
    area = (p_r->pt1.row - p_r->pt2.row) * (p_r->pt1.col - p_r->pt2.col);
    area = area >= 0 ? area : 0 - area;
    printf("面积是%d\n",area);
    return 0;
}
结果:
请输入一个水平长方形的位置:2 5 7 13
面积是40

STDC03_day03_07-结构07.ts

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int row,col;
} pt;
void print(pt pt1){
    printf("点的位置是(%d,%d)\n",pt1.row,pt1.col);
}
int main(){
    pt pt1 = {0};
    printf("请输入一个点的位置:");
    scanf("%d%d",&(pt1.row),&(pt1.col));
    print(pt1);
    return 0; 
    
}
结果:
请输入一个点的位置:4 8
点的位置是(4,8)

结构体类型的变量可以直接作为形式参数使用
直接使用结构体变量做形式参数会导致时间和空间的浪费(补充:计算机在函数调用过程中,计算机会将实参的内容复制到形参中去,如果结构体的内容多,花费的时间就长)
采用结构体指针作为形式参数可以避免这个问题

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int row,col;
} pt;
/*void print(pt pt1){
    printf("点的位置是(%d,%d)\n",pt1.row,pt1.col);
}*/
//补充:函数在执行过程中有可能修改结构体指针形参所捆绑的存储区
//补充:声明指针形参的时候尽可能加const关键字,避免被调函数修改主函数里的存储区
//补充:把一个结构体数据从调用函数传递给被调函数
void print(const pt *p_pt){		
    printf("点的位置是(%d,%d)\n",p_pt->row,p_pt->col);
}
int main(){
    pt pt1 = {0};
    printf("请输入一个点的位置:");
    scanf("%d%d",&(pt1.row),&(pt1.col));
    print(&pt1);
    return 0; 
    
}
结果:
请输入一个点的位置:4 9
点的位置是(4,9)

补充:结构体指针做形式参数的时候尽量使用const关键字声明

有时需要将结构体数据从被调函数传递给调用函数

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int row,col;
} pt;
/*void print(pt pt1){
    printf("点的位置是(%d,%d)\n",pt1.row,pt1.col);
}*/
//补充:从键盘中获得点的位置,再将点的位置传递给调用函数
//补充:返回结构体类型的数据,被调函数需要提供一个结构体类型的存储区存放返回值
//补充:函数名称前写上结构体类型名称,表示函数提供一个结构体类型存储区存放返回值
pt read(void){	
    pt pt1 = {0};
    printf("请输入一个点的位置:");
    scanf("%d%d",&(pt1.row),&(pt1.col));
    return pt1; 	//补充:通过返回值将被调函数数据传递给调用函数
}
//补充:函数在执行过程中有可能修改结构体指针形参所捆绑的存储区
//补充:声明指针形参的时候尽可能加const关键字,避免被调函数修改主函数里的存储区
//补充:把一个结构体数据从调用函数传递给被调函数
void print(const pt *p_pt){
    printf("点的位置是(%d,%d)\n",p_pt->row,p_pt->col);
}
int main(){
    pt pt1 = {0};
    pt1 = read();
    print(&pt1);
    return 0;    
}
结果:
请输入一个点的位置:4 6
点的位置是(4,6)

补充:在read函数调用的时候,结构体数据发生了两次复制,第一次return把pt1中的内容放在返回值专用的存储区中,第二次pt1 = read();把专门用来存放返回值的存储区中的内容复制给pt1

可以直接把结构体变量作为返回值使用,这个时候需要被调用函数提供一个结构体类型的存储区用来存放返回值

STDC03_day03_08-结构08.ts

这也会造成时间和空间的浪费
使用结构体存储区的地址作为返回值可以避免这个问题(这个时候被调用函数需要提供一个结构体指针存储区记录这个返回值)

/CSD1702/biaoc/day13 02struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int row,col;
} pt;
/*void print(pt pt1){
    printf("点的位置是(%d,%d)\n",pt1.row,pt1.col);
}*/
/*pt read(void){
    pt pt1 = {0};
    printf("请输入一个点的位置:");
    scanf("%d%d",&(pt1.row),&(pt1.col));
    return pt1;
}*/
//补充:提供一个结构体指针存储区存放返回的地址
pt *read(pt *p_pt){	//补充:增加一个结构体指针的形式参数,和调用函数的结构体存储区捆绑,把得到的数据直接记录在调用函数提供的存储区,这样就不用声明局部变量了
    //pt pt1 = {0};	//补充:pt1是一个局部变量,read函数结束后局部变量不能用,很少使用静态变量
    printf("请输入点的位置:");
    scanf("%d%d",&(p_pt->row),&(p_pt->col));
    return p_pt;	//补充:把参数当做返回值使用,不要使用局部结构体变量的地址作为返回值
}
void print(const pt *p_pt){
    printf("点的位置是(%d,%d)\n",p_pt->row,p_pt->col);
}
int main(){
    pt pt1 = {0},*p_pt = NULL;	//补充:结构体指针,用指针记录read函数返回值
    p_pt = read(&pt1);
    print(p_pt);
    print(&pt1);
    return 0;     
}
结果:
请输入点的位置:4 7
点的位置是(4,7)
点的位置是(4,7)

不要使用局部结构体变量的地址作为返回值

STDC03_day03_09-结构09.ts

练习:
编写程序计算长方形面积,一个函数用来从键盘得到水平长方形的位置,一个函数用来计算面积

/CSD1702/biaoc/day13 04struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int row,col;
} pt;	//补充:结构体,记录点的位置
typedef struct {
    pt pt1,pt2;
} rect;	//补充:结构体,记录点的个数
//补充:read函数从键盘得到水平长方形的位置,另外一个函数计算面积,read函数需要把得到的位置传递给调用函数
//补充:使用一个结构体存储区地址作为返回值,
rect *read(rect *p_rect){	//补充:结构体指针形参,捆绑一个存储区,这里不能加const关键字
    printf("请输入水平长方形的位置:");
    scanf("%d%d%d%d",&(p_rect->pt1.row),&(p_rect->pt1.col),&(p_rect->pt2.row),&(p_rect->pt2.col));
    return p_rect;	
}
//补充:计算水平长方形的面积,
int area(const rect *p_rect){	//补充:不会修改指针形参的存储区
    int ret = (p_rect->pt1.row - p_rect->pt2.row) * (p_rect->pt1.col - p_rect->pt2.col);
    return ret >= 0 ? ret : 0 -  ret;
}
int main(){
    rect r = {0},*p_r = NULL;
    p_r = read(&r);
    printf("面积是%d\n",area(p_r));
    return 0;     
}
结果:
请输入水平长方形的位置:3 6 4 9
面积是3

讨论结构体存储区大小

/CSD1702/biaoc/day13 05struct.c

/*
    数据对齐和补齐演示
*/
#include <stdio.h>
typedef struct{
    char buf[2];//补充:2个字节,只能占用某一组存储区前2个字节或者后2个字节
    int num;	//补充:4个字节,数据对齐,char buf[2]要占4个字节
} tmp;
int main(){
    printf("sizeof(tmp)是%d\n",sizeof(tmp));
    return 0;
}
结果:
sizeof(tmp)8

一个存储区的地址一定是它自身大小的整数倍(双精度浮点类型存储区的地址只需要是4的整数倍就可以了),这个规则叫数据对齐
结构体的子存储区通常也需要遵守数据对齐的规定
数据对齐可能导致结构体的子存储区之间有空隙

/CSD1702/biaoc/day13 05struct.c

/*
    数据对齐和补齐演示
*/
#include <stdio.h>
typedef struct{
    char buf[2];//补充:2个字节,只能占用某一组存储区前2个字节或者后2个字节
    int num;	//补充:4个字节,数据对齐,char buf[2]要占4个字节
} tmp;
typedef struct{
    char ch;	//补充:char 1个字节,数据对齐,实际占4个字节存储区
    int num;	//补充:int 4个字节,实际占4个字节存储区
    char ch1;	//补充:char 1个字节,数据补齐,实际占4个字节存储区
} tmp1;
int main(){
    printf("sizeof(tmp)是%d\n",sizeof(tmp));
    printf("sizeof(tmp1)是%d\n",sizeof(tmp1));
    return 0;
}
结果:
sizeof(tmp)8
sizeof(tmp1)12

结构体存储区大小必须是它所包含的占地最大的基本类型子存储区大小的整数倍(如果这个基本类型子存储区的类型是double则结构体存储区的大小只需要是4的整数倍)
这个规则叫数据补齐
数据补齐可能造成结构体最后有浪费的字节

由于数据对齐和补齐的存在,在声明结构体类型时应该尽量把占地小的子存储区放在前面,占地大的子存储区放在后面

STDC03_day03_10-结构10.ts

练习:
编写函数判断一个点和一个圆的位置关系,返回值是0表示点在圆里面,返回值是1表示点在圆上,返回值是2表示点在圆外

(行号差的平方 + 列号差的平方)然后再开方

/CSD1702/biaoc/day13 06struct.c

/*
    结构体练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;              //结构体记录点的信息
typedef struct{
    pt center;	//记录圆心
    int radius;	//记录半径
} circle;          //结构体记录圆的信息
int len2(const pt *p_pt1,const pt *p_pt2){    //计算两点距离和的平方
    return (p_pt1->row - p_pt2->row) * (p_pt1->row - p_pt2->row) + (p_pt1->col - p_pt2->col) * (p_pt1->col - p_pt2->col);
}
int relation(circle *p_cl,pt *p_pt){  //计算点和圆之间的关系
    int tmp = len2(&(p_cl->center),p_pt);
    if(tmp > p_cl->radius * p_cl->radius){
        return 2;	//点在圆外
    }
    else if(tmp < p_cl->radius * p_cl->radius){
        return 0;	//点在圆内
    }
    else {
        return 1;	//点在圆上
    }
}
int main(){
    int ret = 0;
    circle cl = {0};
    pt pt1 = {0};
    printf("请输入一个圆的位置:");
    scanf("%d%d%d",&(cl.center.row),&(cl.center.col),&(cl.radius));
    printf("请输入一个点的位置:");
    scanf("%d%d",&(pt1.row),&(pt1.col));
    ret = relation(&cl,&pt1);
    if(!ret){
        printf("点在圆内\n");
    }
    else if(ret == 1){
        printf("点在圆上\n");
    }
    else{
        printf("点在圆外\n");
    }
    return 0;
}
结果:
请输入一个圆的位置:4 4 6
请输入一个点的位置:4 7
点在圆内
请输入一个圆的位置:4 4 6
请输入一个点的位置:4 10
点在圆上
请输入一个圆的位置:4 4 6
请输入一个点的位置:4 12
点在圆外

预习:
​ 1.枚举和联合
​ 2.二级指针
​ 3.函数指针
​ 4.动态内存分配

day4: 联合和枚举 、 指针高级 、 标准函数库

STDC03_day04_01-联合和枚举01.ts

枚举也可以用来创建新的数据类型
枚举类型的存储区就是整数类型的存储区,枚举类型存储区里只能放有限的几个整数
枚举类型也需要先声明然后才能使用
声明枚举类型的时候需要提供几个名称,计算机为每个名称分配一个整数。只有这些整数才能记录到这种枚举类型的存储区里
不同枚举类型所能记录的整数范围不同
声明枚举类型的时候需要使用enum关键字

/CSD1702/biaoc/day14 01enum.c

/*
    枚举类型演示
*/
#include <stdio.h>
int main (){
    enum season {	//补充:声明枚举时内部需要列出多个名称,名称见用逗号分隔
        CHUN,
        XIA,
        QIU,
        DONG};//枚举声明
    return 0;
}

可以使用enum season作为类型名称使用,也可以使用typedef给枚举取别名,通常不需要声明枚举类型变量,可以省略枚举名称season

/*
    枚举类型演示
*/
#include <stdio.h>
int main (){
    enum {CHUN,XIA,QIU,DONG};//枚举声明,第一个名称分配整数0,向后依次递增
    printf("QIU是%d\n",QIU);
    return 0;
}
结果:
QIU是2

计算机给枚举中第一个名称分配整数0,向后依次递增
可以在声明枚举类型的时候制定某个名称对应的整数,这个名称后面的名称对应的整数也会随之发生变化

/CSD1702/biaoc/day14 01enum.c

/*
    枚举类型演示
*/
#include <stdio.h>
int main (){
    enum {CHUN,XIA = 5,QIU,DONG};//枚举声明
    printf("QIU是%d\n",QIU);
    return 0;
}
结果:
QIU是6

枚举类型可以永远不用,它可以让程序编写更容易,也可以定义整数

STDC03_day04_02-联合和枚举02.ts

联合也可以用来声明新的数据类型
声明联合的时候需要使用union关键字

/CSD1702/biaoc/day14 02union.c

/*
    联合演示
*/
#include <stdio.h>
typedef union /*tmp*/{	//补充:声明联合,省略联合本身名称
    char ch;
    int num;
    float fnum;
} tmp;	//补充:联合别名
int main (){
    tmp tmp_union = {0};	//补充:声明联合变量
    printf("&(tmp_union.ch)是%p\n",&(tmp_union.ch));
    printf("&(tmp_union.num)是%p\n",&(tmp_union.num));
    printf("&(tmp_union.fnum)是%p\n",&(tmp_union.fnum));
    return 0;
}
结果:
&(tmp_union.ch)000000000062FE40
&(tmp_union.num)000000000062FE40
&(tmp_union.fnum)000000000062FE40

联合的所有子存储区互相重叠
联合的所有子存储区开始地址一样
联合的存储区可以当做多种不同类型的存储区使用,每个成员变量代表了联合的一种使用方式
联合存储区的大小应该是最大成员变量的大小

/CSD1702/biaoc/day14 02union.c

/*
    联合演示
*/
#include <stdio.h>
typedef union /*tmp*/{
    char ch;
    int num;
    float fnum;
} tmp;
int main (){
    tmp tmp_union = {0};
    printf("&(tmp_union.ch)是%p\n",&(tmp_union.ch));
    printf("&(tmp_union.num)是%p\n",&(tmp_union.num));
    printf("&(tmp_union.fnum)是%p\n",&(tmp_union.fnum));
    printf("sizeof(tmp)是%d\n",sizeof(tmp));
    return 0;
}
结果:
&(tmp_union.ch)000000000062FE40
&(tmp_union.num)000000000062FE40
&(tmp_union.fnum)000000000062FE40
sizeof(tmp)4

STDC03_day04_03-指针高级01.ts

用来记录普通类型存储区地址的指针叫一级指针
指针变量存储区的地址可以记录在二级指针
声明二级指针的时候需要使用**

/CSD1702/biaoc/day14 03ptr.c

/*
    二级指针演示
*/
#include <stdio.h>
int main (){
    int num = 0;
    int *p_num = &num;
    int **pp_num = &p_num;          //二级指针
    **pp_num = 10;
    printf("num是%d\n",num);
    *pp_num = NULL;
    printf("p_num是%p\n",p_num);
    return 0;
}
结果:
视频:
num是10
p_num是(nil)
    
DEV C++:
num是10
p_num是0000000000000000      //空地址

在二级指针名称变量前使用**可以找到捆绑的普通变量存储区
在二级指针变量名称前使用*可以找到捆绑的一级指针存储区

二级指针可以代表指针数组,但不能代表二维数组

STDC03_day04_04-指针高级02.ts

/CSD1702/biaoc/day14 04ptr.c

/*
    二级指针演示
*/
#include <stdio.h>
//int main (int argc,char *argv[])
//补充:数组作为形参的时候,真正的形参并不是一个数组,真正的形参是一个当做数组用的指针
int main (int argc,char **argv){
    int num = 0;
    for(num = 0;num <= argc - 1;num++){
        printf("%s\n",*(argv + num));
    }
    return 0;
}
结果:
gcc  04ptr.c
./a.out abc def xyz

./a.out
abc
def
xyz

void作为类型的指针叫作无类型指针,声明无类型指针的时候只用了一个*
无类型指针有可能实际代表的是一个二级指针
补充:常见用法是把二级指针当做形式参数使用,好处是二级指针形式参数可以和调用函数一级指针存储区捆绑,可以在被调函数里通过二级指针形式参数把一个地址数据传递给调用函数
二级指针形参和调用函数一级指针存储区捆绑,被调函数就可以把一个地址放在调用函数的一级指针存储区里

被调用函数可以通过二级指针形式参数把一个地址数据传递给调用函数

/CSD1702/biaoc/day14 05ptr.c

/*
    二级指针演示
*/
#include <stdio.h>
//补充:被调用函数通过二级指针形参把一个地址数据传递给调用函数
void set_null(int **pp_num){	//补充:被调函数二级指针形参
    *pp_num = NULL;	//补充:p_num = NULL;在函数内不能使用p_num指针变量名称
}
int main (int argc,char **argv){
    int num = 0;
    int *p_num = &num;	//补充:一级指针和普通变量捆绑
    set_null(&p_num);
    printf("p_num是%p\n",p_num);
    return 0;
}
结果:
p_num是0000000000000000
//补充:演示了如何使用二级指针形参,从被调函数向调用函数传递一个地址数据

练习:
编写函数从两个圆里找到面积比较大的并把它传递给调用函数
不可以使用返回值

STDC03_day04_05-指针高级03.ts

/CSD1702/biaoc/day14 06ptr.c

/*
    二级指针练习
*/
#include <stdio.h>
typedef struct{
    int row,col;
} pt;
typedef struct{
    pt center;
    int radius;
} circle;
//补充:函数不可以使用返回值
//补充:从调用函数得到两个已知的圆,使用结构体指针形参
//补充:函数中不会修改这两个指针所捆绑的存储区内容,可以加const关键字
//补充:需要把结构体存储区的地址传递给调用函数,使用二级指针形参把一个地址数据从被调函数传递给调用函数
//补充:选出的圆要记录在一个结构体类型存储区中,把结构体类型存储区的地址传递给调用函数
//补充:(p_cl1->radius >= p_cl2->radius ? p_cl1 : p_cl2)返回要传递的地址,需强制转换
//补充:*pp_cl二级指针形参加*表示主函数中一级指针所代表的存储区
void max(const circle *p_cl1,const circle *p_cl2,circle **pp_cl){
    *pp_cl = (circle *)(p_cl1->radius >= p_cl2->radius ? p_cl1 : p_cl2);
}
int main (int argc,char **argv){
    circle cl1 = {0},cl2 = {0},*p_cl = NULL;
    printf("请输入一个圆的位置:");
    scanf("%d%d%d",&(cl1.center.row),&(cl1.center.col),&(cl1.radius));
    printf("请输入另一个圆的位置:");
    scanf("%d%d%d",&(cl2.center.row),&(cl2.center.col),&(cl2.radius));
    max(&cl1,&cl2,&p_cl);
    printf("面积比较大的圆是((%d,%d),%d)\n",p_cl->center.row,p_cl->center.col,p_cl->radius);
    return 0;
}
结果:
请输入一个圆的位置:3 3 7
请输入另一个圆的位置:6 6 5
面积比较大的圆是((3,3),7)

STDC03_day04_06-指针高级04.ts

C语言里函数也有地址
函数名称可以用来表示函数的地址
函数指针可以用来记录函数的地址
函数指针也需要先声明然后才能使用
函数指针声明可以根据函数声明变化得到

/CSD1702/biaoc/day14 07ptr.c

/*
    函数指针演示
*/
#include <stdio.h>
int add(int num,int num1){	//补充:声明一个加法函数
    return num + num1;
}
int main (){
    int (*p_func)(int,int) = NULL;   //函数指针声明语句 初始化为空指针
    p_func = add;	//补充:把函数名称当做地址赋值给函数指针
    printf("p_func是%p\n",p_func);
    printf("p_func(3,8)是%d\n",p_func(3,8));
    return 0;
}
结果:
p_func是0000000000401530
p_func(3,8)11

函数指针也分类型,不同类型的函数指针适合与不同类型的函数捆绑

函数指针可以用来调用函数

微软把win7系统分成几千个文件,补丁修复。有可能从一个文件中调用另外一个文件的函数,不能使用函数名称调用,这个时候需要用到函数指针,通过某些方法让函数指针和另外一个文件中的函数捆绑

一种制作游戏外挂的思路

视频顺序错误

STDC03_day04_08-指针高级06.ts

函数指针一种重要的使用方法

/CSD1702/biaoc/day14 08ptr.c

/*
    函数指针演示
*/
#include <stdio.h>
//补充:写一个函数,把数组中所有存储区内容显示在屏幕上
//补充:不需要返回值,指针形参代表数组
void print(int *p_num,int size){	
    int num = 0;
    for (num = 0;num <= size - 1;num++){
        printf("%d ",*(p_num + num));
    }
    printf("\n");
}
int main(){
    int arr[] = {1,2,3,4,5}; //补充:准备一个数组
    print(arr,5);
    return 0;
}
结果:
1 2 3 4 5

将print函数根据循环和显示拆分成两个部分
手机中的应用和操作系统,应用想用的时候装上,不想用的时候删掉
分别写两个函数,一个函数完成显示,一个函数完成循环

/CSD1702/biaoc/day14 08ptr.c

/*
    函数指针演示
*/
#include <stdio.h>
void print(int *p_num,int size){
    int num = 0;
    for (num = 0;num <= size - 1;num++){
        printf("%d ",*(p_num + num));
    }
    printf("\n");
}
//补充:负责完成print函数的显示功能
void print_cb(int *p_num){  //回调函数
    printf("%d ",*p_num);
}
//补充:负责完成print函数的循环功能
//补充:改用函数指针调用print_cb,把函数指针当做形参使用
void for_each(int *p_num,int size,void (*p_func)(int *)){
    int num = 0;
    //void (*p_func)(int *) = print_cb;
    for(num = 0;num <= size - 1;num++){
        p_func(p_num + num);
    }
}
int main(){
    int arr[] = {1,2,3,4,5};
    print(arr,5);
    for_each(arr,5,print_cb);	//补充:函数名称就是函数地址
    printf("\n");
    return 0;
}
结果:
1 2 3 4 5
1 2 3 4 5

函数指针可以作为形式参数使用
会作为实际参数使用的函数叫回调函数

STDC03_day04_09-标准函数库01.ts

使用for_each函数将数组中所有的存储区内容变成相反数

/CSD1702/biaoc/day14 08ptr.c

/*
    函数指针演示
*/
#include <stdio.h>
//补充:负责完成print函数的显示功能
//补充:print_cb和neg_cb都要和函数指针形参捆绑,格式必须一样,neg_cb不能有const关键字
void print_cb(int *p_num){  //回调函数
    printf("%d ",*p_num);
}
//补充:将数组存储区内容变成相反数
void neg_cb(int *p_num){
    *p_num = 0 - *p_num;
}
//补充:负责完成print函数的循环功能
//补充:改用函数指针调用print_cb,把函数指针当做形参使用
void for_each(int *p_num,int size,void (*p_func)(int *)){
    int num = 0;
    for(num = 0;num <= size - 1;num++){
        p_func(p_num + num);
    }
}
int main(){
    int arr[] = {1,2,3,4,5};
    print(arr,5);
    for_each(arr,5,print_cb);
    printf("\n");
    for_each(arr,5,neg_cb);
    for_each(arr,5,print_cb);
    printf("\n");
    return 0;
}
结果:
1 2 3 4 5
1 2 3 4 5
-1 -2 -3 -4 -5

补充:函数指针作为形参的好处,可以把一个通用功能和一个专用功能合在一起,一起来完成一件事
通用功能函数往往是比较高级的工程师写的,专用功能函数一般是负责某一部分的工程师写的
先写好通用功能,然后再写好专用功能

STDC03_day04_10-标准函数库02.ts

可以在程序运行的时候临时决定需要分配多少存储区
这种分配存储区的方法叫动态分配内存
为了使用动态分配内存需要用到一组标准函数,为了使用这些标准函数需要包含stdlib.h头文件

malloc函数可以动态分配一组连续的字节
这个函数需要一个整数类型参数表示希望分配的字节个数
这个函数的返回值表示分配好的第一个字节的地址
如果分配失败则返回值是NULL
这个函数把返回值放在一个无类型指针存储区里,需要首先转换成有类型指针然后才能使用

/CSD1702/biaoc/day14 09heap.c

/*
    动态分配内存演示
*/
#include <stdio.h>
#include <stdlib.h>
int main(){
    int *p_num = (int *)malloc(5 * sizeof(int));
    if(p_num){
        //使用动态分配内存
    }
    return 0;
}

STDC03_day04_11-标准函数库03.ts

计算机不会自动回收动态分配内存
如果程序中不再需要使用某些动态分配内存,就需要使用专门的语句把他们还给计算机
free函数用来释放动态分配的内存
free函数需要动态分配的第一个字节的地址作为参数
一起分配的内存必须一起释放
如果使用指针作为参数调用free函数则函数结束后指针将成为野指针,必须恢复成空指针

/CSD1702/biaoc/day14 09heap.c

/*
    动态分配内存演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
    int *p_num = NULL,num = 0;
    srand(time(0));
    p_num = (int *)malloc(5 * sizeof(int));
    /*if(p_num){
        //使用动态分配内存
        free(p_num);
        p_num = NULL;
    }*/
    if(!p_num){
        return 0;
    }
    for(num = 0;num <= 4;num++){
        *(p_num + num) = rand() % 36 + 1;
    }
    for(num = 0;num <= 4;num++){
        printf("%d ",*(p_num + num));
    }
    printf("\n");
    //使用动态分配内存
    free(p_num);
    p_num = NULL;
    return 0;
}
结果:随机数
32 33 35 16 28

STDC03_day04_12-标准函数库04.ts

调用函数可以使用被调用函数动态分配的内存

/CSD1702/biaoc/day14 10heap.c

/*
    动态分配内存演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int *create(int size){
    int num = 0;
    int *p_num = (int *)malloc(size * sizeof(int));
    if(p_num){
        for(num = 0;num <= size -1;num++){
            *(p_num + num) = rand() % 36 + 1;
        }
    }
    return p_num;
}
int main(){
    int *p_num = NULL,num = 0;
    srand(time(0));
    p_num = create(7);
    if(p_num){
        for(num = 0;num <= 6;num++){
            printf("%d ",*(p_num + num));            
        }
        printf("\n");
        free(p_num);
        p_num = NULL;
    }    
    return 0;
}
结果:随机数
8 28 15 20 9 25 35

练习:
编写函数计算一个水平长方形中心点的位置并把结果传递给调用函数
用动态分配内存记录中心点的位置

STDC03_day04_07-指针高级05.ts

/CSD1702/biaoc/day14 11heap.c

/*
    动态分配内存演示
*/
#include <stdio.h>
#include <stdlib.h>
typedef struct{
    int row,col;
} pt;
typedef struct{
    pt pt1,pt2;
} rect;
pt *midpt(const rect *p_r){
    pt *p_mid = (pt *)malloc(sizeof(pt));
    if(p_mid){
        p_mid->row = (p_r->pt1.row + p_r->pt2.row) / 2;
        p_mid->col = (p_r->pt1.col + p_r->pt2.col) / 2;
    }
    return p_mid;
}
int main(){
    pt *p_pt = NULL;
    rect r = {0};
    printf("请输入水平长方形的位置:");
    scanf("%d%d%d%d",&(r.pt1.row),&(r.pt1.col),&(r.pt2.row),&(r.pt2.col));
    p_pt = midpt(&r);
    if(p_pt){
        printf("中间点的位置是(%d,%d)\n",p_pt->row,p_pt->col);
        free(p_pt);
        p_pt = NULL;
    }
    return 0; 
}
结果:
请输入水平长方形的位置:3 7 5 13
中间点的位置是(4,10)

预习:
​ 1.文件操作

day5: 输入输出函数01

STDC03_day05_01-标准函数库05.ts

calloc函数也可以动态分配内存
这个函数可以把动态分配的所有存储区内容清0
为了使用这个函数也需要包含stdlib.h头文件
这个函数需要两个参数,第一个参数表示希望分配的存储区个数,第二个参数表示单个存储区的大小
这个函数的返回值表示分配好的第一个存储区的地址
这个函数也可能失败,如果失败则返回值是NULL

realloc函数可以调整一段动态分配内存的大小
一般不要使用这个函数

STDC03_day05_02-输入输出函数01.ts

文件都是采用二进制方式记录数字
如果文件里所有二进制数据都有对应的字符,就把这种文件叫做文本文件
除了文本文件以外的都叫做二进制文件

文本文件和二进制文件分别采用不同的方式进行操作(文本文件也可以当作二进制文件进行操作)

/CSD1702/biaoc/day15 01file.c

/*
    文件操作演示
*/
#include <stdio.h>
int main(){
    FILE *p_file = fopen("a.txt","w");
    /*if(p_file){
        //操作文件       
        fclose(p_file);
        p_file = NULL;
    }*/
    if(!p_file){
        return 0;
    }
    //操作文件       
    fclose(p_file);
    p_file = NULL;    
    return 0;
}

文件操作基本步骤:
1.打开文件(fopen)
2.操作文件(fread/fwrite)
3.关闭文件(fclose)

STDC03_day05_03-输入输出函数02.ts

fopen函数用来打开文件,他需要两个参数
1.代表文件路径
2.代表打开方式(决定可以在程序中对文件进行什么操作)

打开方式有如下选择
“r” 只能查看文件内容不能修改,只能从文件头开始查看,如果文件不存在打开会失败
“r+” 比"r"多了修改功能
“w” 只能修改内容不能查看,只能从文件头开始修改,如果文件不存在就创建文件,如果文件已经存在就把文件内容都删除
“w+” 比"w"多了查看文件内容的功能
“a” 只能修改不能查看,修改方式是在文件原有内容后面追加新内容,如果文件不存在就创建文件,如果文件已经存在不会修改文件原有内容
“a+” 比"a"多了查看功能
“b” 也是一种打开方式,它可以和前面的任何一种打开方式混合使用,它表示程序中要以二进制方式操作文件

fopen函数的返回值应该记录在文件指针里,程序中只能用文件指针代表文件
fopen函数有可能失败,如果失败就返回NULL

一旦完成文件的所有操作以后必须使用fclose函数关闭文件
fclose函数需要文件指针作为参数
文件关闭以后文件指针成为野指针,必须恢复成空指针

STDC03_day05_04-输入输出函数03.ts

文件操作分成两种
1.把内存里一组连续存储区的内容拷贝到文件里,这个叫写文件(fwrite)
2.把文件里一组连续存储区的内容拷贝到内存里,这个叫读文件(fread)

freadfwrite都是以二进制方式操作文件的

这两个函数都需要四个参数
1.内存里第一个存储区的地址
2.内存里单个存储区的大小
3.希望操作的存储区个数
4.文件指针

这两个函数都有返回值,它们的返回值表示实际操作的存储区个数

/CSD1702/biaoc/day15 02file.c

/*
    文件操作演示
*/
#include <stdio.h>
int main(){
    int arr[] = {1,2,3,4,5},size = 0;
    FILE *p_file = fopen("a.bin","wb");
    if(p_file){
        size = fwrite(arr,sizeof(int),5,p_file);
        printf("写入%d个整数类型存储区\n",size);
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
结果:
写入5个整数类型存储区

STDC03_day05_05-输入输出函数04.ts

/CSD1702/biaoc/day15 03file.c

/*
    文件操作演示
*/
#include <stdio.h>
int main(){
    int arr[] = {0},size = 0,num = 0;
    FILE *p_file = fopen("a.bin","rb");
    if(p_file){
        size = fread(arr,sizeof(int),5,p_file);
        printf("一共获得%d个整数\n",size);
        for(num = 0;num <= 4;num++){
            printf("%d ",arr[num]);
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
结果:
一共获得5个整数
1 2 3 4 5

/CSD1702/biaoc/day15 04file.c

/*
    文件操作演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    int arr[] = {1,2,3,4,5},num = 0;
    char buf[10] = {0};
    FILE *p_file = fopen("a.txt","wb");
    if(p_file){
        for(num = 0;num <= 4;num++){
            sprintf(buf,"%d ",arr[num]);
            fwrite(buf,sizeof(char),strlen(buf),p_file);
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
结果将1 2 3 4 5 写入到a.txt文件中

STDC03_day05_06-输入输出函数05.ts

fprintf函数可以把数据按照格式记录到文本文件里
这个函数的第一个参数是一个文件指针,后面的参数和printf函数的参数一样
这个函数以文本方式操作文件

/CSD1702/biaoc/day15 04file.c

/*
    文件操作演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    int arr[] = {1,2,3,4,5},num = 0;
    char buf[10] = {0};
    //FILE *p_file = fopen("a.txt","wb");
    FILE *p_file = fopen("a.txt","w");
    if(p_file){
        for(num = 0;num <= 4;num++){
            /*sprintf(buf,"%d ",arr[num]);
            fwrite(buf,sizeof(char),strlen(buf),p_file);*/
            fprintf(p_file,"%d ",arr[num]);
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
结果将1 2 3 4 5 写入到a.txt文件中

fscanf函数可以从文本文件里获得数字并记录到存储区里
这个函数的第一个参数是一个文件指针,后面的参数就是scanf函数的参数
这个函数也是以文本方式操作文件

/CSD1702/biaoc/day15 05file.c

/*
    文件操作演示
*/
#include <stdio.h>
#include <string.h>
int main(){
    int arr[5] = {0},num = 0;
    FILE *p_file = fopen("a.txt","r");
    if (p_file){
        for(num = 0;num <= 4;num++){
            fscanf(p_file,"%d",arr + num);
        }
        for(num = 0;num <= 4;num++){
            printf("%d ",arr[num]);
        }
        printf("\n");
        fclose(p_file);
        p_file = NULL;
    }
}
结果:
1 2 3 4 5

STDC03_day05_07-输入输出函数06.ts

练习:
编写程序从键盘得到多个人员信息并把它们都记录到文件里
每个人员信息包括整数类型的id,float类型的工资和姓名
人员信息个数不确定
文件内容可以追加

一个程序把人员信息记录到文件里
另外一个程序从文件里得到人员信息并显示在屏幕上

/CSD1702/biaoc/day15 06file.c

/*
    人员信息练习
*/
#include <stdio.h>
#include <string.h>
typedef struct{
    int id;
    float salary;
    char name[10];
} person;
int main(){
    int choice = 0;
    person prsn = {0};
    FILE *p_file = fopen("penson.bin",'ab');
    if (p_file){
        while (1){
            printf("请输入id:");
            scanf("%d",&(prsn.id));
            printf("请输入工资:");
            scanf("%g",&(prsn.salary));
            scanf("%*[^\n]");
            scanf("%*c");
            printf("请输入姓名:");
            fgets(prsn.name,10,stdin);
            if (strlen(prsn.name) == 9 && prsn.name[8] != "\n"){
                scanf("%8[^\n]");
                scanf("%*c");
            }
            fwrite(&prsn,sizeof(person),1,p_file);
            printf("是否需要输入下一个人员信息?0表示不需要,1表示需要");
            scanf("%d",&choice);
            if(!choice){
                break;
            }
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
结果:
请输入id:1
请输入工资:1.1
请输入姓名:abc
是否需要输入下一个人员信息?0表示不需要,1表示需要1
请输入id:2
请输入工资:2.2
请输入姓名:def
是否需要输入下一个人员信息?0表示不需要,1表示需要0

STDC03_day05_08-输入输出函数07.ts

/CSD1702/biaoc/day15 07file.c

/*
    文件练习
*/
#include <stdio.h>
#include <string.h>
typedef struct{
    int id;
    float salary;
    char name[10];
} person;
int main(){
    int size = 0;
    person prsn = {0};
    FILE *p_file = fopen("person.bin","rb");
    if (p_file){
        while (1){
            size = fread(&prsn,sizeof(person),1,p_file);
            if(!size){
                break;
            }
            printf("id是%d\n",prsn.id);
            printf("工资是%g\n",prsn.salary);
            printf("姓名是%s\n",prsn.name);
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}

STDC03_day05_09-输入输出函数08.ts

计算机里为每个文件保留了一个整数,这个整数表示下一次读写文件的开始位置
这个位置一定在两个相邻的字节之间
这个整数的数值就是文件头到这个位置之间包含的字节个数
这个整数叫做文件的位置指针
每次从文件中读n个字节或向文件里写n个字节后,位置指针都会向后移动n个字节

ftell函数可以获得当前位置指针的数值

/CSD1702/biaoc/day15 08file.c

/*
    位置指针演示
*/
#include <stdio.h>
int main(){
    char ch = 0;
    FILE *p_file = fopen("abc.txt","rb");
    if (p_file){
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fclose(p_file);
        p_file = NULL;
    }
}
结果:
位置指针的数值是0
a
位置指针的数值是1
b
位置指针的数值是2
c

rewind函数可以把位置指针的数值设置成0

/CSD1702/biaoc/day15 08file.c

/*
    位置指针演示
*/
#include <stdio.h>
int main(){
    char ch = 0;
    FILE *p_file = fopen("abc.txt","rb");
    if (p_file){
        rewind(p_file);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        rewind(p_file);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        rewind(p_file);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        fclose(p_file);
        p_file = NULL;
    }
}
结果:
位置指针的数值是0
a
位置指针的数值是0
a
位置指针的数值是0
a

fseek函数可以把位置指针放到文件中任何位置
fseek函数里需要设置一个基准位置以及目标位置到基准位置的距离

SEEK_SET 0 把文件头作为基准位置
SEEK_CUT 1 把当前位置作为基准位置
SEEK_END 2 把文件尾作为基准位置

如果目标位置在基准位置后面则距离用非负数表示
如果目标位置在基准为止前面则距离用负数表示

距离的数值就是目标位置和基准位置之间包含的字节个数

STDC03_day05_10-输入输出函数09.ts

/CSD1702/biaoc/day15 08file.c

/*
    位置指针演示
*/
#include <stdio.h>
int main(){
    char ch = 0;
    FILE *p_file = fopen("abc.txt","rb");
    if (p_file){
        //rewind(p_file);
        fseek(p_file,2,SEEK_SET);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        //rewind(p_file);
        fseek(p_file,4,SEEK_CUR);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        //rewind(p_file);
        fseek(p_file,-4,SEEK_END);
        printf("位置指针的数值是%ld\n",ftell(p_file));
        fread(&ch,sizeof(char),1,p_file);
        printf("%c\n",ch);
        fclose(p_file);
        p_file = NULL;
    }
}
结果:
位置指针的数值是2
c
位置指针的数值是7
h
位置指针的数值是10
k

练习:
编写程序从person.bin文件里得到所有人员的id并显示在屏幕上

/CSD1702/biaoc/day15 09file.c

/*
    位置指针练习
*/
#include <stdio.h>
typedef struct{
    int id;
    float salary;
    char name[10];
} person;
int main(){
    int id = 0,size = 0;
    FILE *p_file = fopen("abc.txt","rb");
    if (p_file){
        while(1){
            size = fread(&id,sizeof(int),1,p_file);
            if(!size){
                break;
            }
            printf("id是%d\n",id);
            fseek(p_file,sizeof(person) - sizeof(int),SEEK_CUR);
        }
        fclose(p_file);
        p_file = NULL;
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值