一、复合类型:枚举
1.1.枚举的本质就是一堆整数的集合,列表,就是给整数取了个别名,提高代码的可读性,枚举值默认是从0开始,后面的成员依次加1
例如:250(不具体)---通过枚举的方式----->坏蛋BAD EGG(250的别名)
0-------通过枚举的方式------------->红色RED(0的别名)
1-------通过枚举的方式------------->绿色GREEN(1的别名)
2-------通过枚举的方式------------->蓝色BLUE(2的别名)
1.2.声明枚举数据类型的语法:
enum 枚举数据类型名 {枚举值}; (可以不起名)
例如:enum COLOR {RED, GREEN, BLUE};
结果:RED=0,GREEN=1,BLUE=2 等价于给0,1,2取别名
printf("%d %d %d\n", RED, GREEN, BLUE); //0,1,2
例如:
enum COLOR {RED, GREEN=250, BLUE};
结果:RED=0,GREEN=250,BLUE=251 等价于给0,250,251取别名
printf("%d %d %d\n", RED, GREEN, BLUE); //0,250,251
结论:将来程序中再描述红,绿,蓝三种颜色,就不需要用0,1,2(代码可读性非常差),用RED, GREEN,BLUE替换0,1,2
1.3.枚举的经典使用代码方式
vim enum2.c 添加
#include <stdio.h>
/*定义检测函数*/
int check(int a)
{
if(a != 0) {
printf("表示成功了.\n");
return 0; //表示成功
} else {
printf("表示失败了.\n");
return 1; //表示失败
}
}
int main(void)
{
printf("%d\n", check(1));
return 0;
}
提高代码可读性可以采用枚举方法,也就是给0和1取别名:RETURN_OK,RETURN_BAD
改进以后的代码:
vim enum3.c 添加
#include <stdio.h>
//定义枚举数据类型
enum RETURN {RETURN_OK, RETURN_BAD};
//对枚举数据类型取别名
typedef enum RETURN return_t;
/*定义检测函数*/
return_t check(int a)
{
if(a != 0) {
printf("表示成功了.\n");
return RETURN_OK; //表示成功
} else {
printf("表示失败了.\n");
return RETURN_BAD; //表示失败
}
}
int main(void){
printf("%d\n", check(1));
printf("%d\n", check(0));
return 0;
}
二、函数指针
2.1.指针函数
概念:就是一个函数,只是它的返回值是一个指针
例如:int *add(int a, int b)
b)函数名就是整个函数里面代码的首地址,简称函数的首地址
int add(int a, int b)
{printf("%d\n", a);
printf("%d\n", b);
return a + b;
}
add这个函数名就是整个函数add的首地址,就是三条语句的首地址,等于第一条语句,printf函数所在内存的首地址
2.2.函数指针
概念:本质就是一种程序员自己定义的数据类型(跟int,结构体数据类型一样的)它保存着一个函数的地址
2.3.函数指针数据类型声明的语法:(不会分配内存并且大型程序中写头文件)
返回值数据类型 (*函数指针名)(形参表);
例如:
int (*pfunc)(int a, int b); //pfunc就是一种数据类型,当成int型来使用
或 者
typedef int (*pfunc_t)(int a, int b); //对函数指针取别名叫pfunc_t,以后都这么用
2.4.函数指针变量定义语法格式:函数指针名 函数指针变量;
例如:pfunc_t pfunc; //pfunc就是一个函数指针变量,将来保存函数的地址
2.5.函数指针变量的初始化
pfunc_t pfunc = add; //定义并且初始化函数指针变量,指向add函数
等价于
int (*pfunc)(int a, int b) = add; //pfunc指向add函数,此种写法繁琐
2.6.通过函数指针变量来访问指向的函数,通过函数指针变量来调用指向的函数
语法格式:函数指针变量名(实参表);
2.7.函数指针总结
a)函数指针建议用typedef取别名
b)函数指针的返回值和形参表务必要和指向的函数的返回值和形参表一致
c)回调函数:一个函数可以被当成参数传递给别的函数,这个函数的参数必须是函数指针
来保存回调函数的地址(例如:add)
2.8.函数指针经典代码演示:
vim pfunction3.c
#include <stdio.h>
typedef int (*pfunc_t)(int , int);
int add(int a, int b){return a + b;}
int sub(int a, int b){return a - b;}
int mul(int a, int b){return a*b;}
int div(int a, int b) {return a/b;}
int main(void)
{
//目的:把所有的函数调用一遍
//1.定义函数指针数组,每个元素是一个函数指针,是一个函数的地址
pfunc_t pfunc_array[] = {add, sub, mul, div, NULL};
//2.挨个调用
for(pfunc_t *pfunc = pfunc_array; *pfunc; pfunc++) {
int ret = (*pfunc)(100, 200);
printf("ret = %d\n", ret);
}
return 0;
}
结论:将来如果想让main函数调用一堆的函数,只需将函数放到数组中即可,将来for循环自动帮你挨个调用!
三、多级指针
3.1.回顾一级指针:指向一个普通变量的内存区域
例如:int a = 250;
int *p = &a; //p保存着a变量的首地址
printf("a的首地址%p, a的值是%d\n", p, *p);
3.2.二级指针定义:指向一级指针的指针,也就是存放着一级指针变量的地址
3.3.定义二级指针变量的语法格式:数据类型 **二级指针变量名 = 一级指针的地址;
例如:
int a = 250;
int *p = &a;
int **pp = &p;
//读查看
printf("p的地址是%p, a的地址是%p, a的变量是%d", pp, *pp, **pp);
//写修改
**pp = 350;
3.4.二级指针和字符串
经典的笔试题:编写一个函数实现两个字符串的交互
例如:
char *pa = "hello";
char *pb = "world";
目标:pa->"world", pb->"hello",指针互换
注意:通过pa和pb指针是无法修改字符串的值
参考代码:swap.c
二级指针的交换,对比:
3.5.二级指针和字符指针数组
a)回顾:字符指针数组形式
char *p[] = {"hello", "world"}; //p[0]保存字符串"hello"的首地址
//p[1]保存字符串"world"的首地址
或者
char *p1 = "hello";
char *p2 = "world";
char *p[] = {p1, p2};
显然p具有二级指针的意味,里面每个元素是一个指针,而这个指针又指向一个字符串数据
问:如何定义一个二级字符指针指向字符指针数组呢?
答:char **pstr = p;
printf("%s %s %s %s %s %s\n",
p[0], p[1], *(pstr+0), *(pstr+1), pstr[0], pstr[1]);
结论:二级指针和字符指针数组的等价关系:
char **pstr 等价于 char *p[元素个数]
执行结果:
3.6.实际开发,产品代码的主函数main将来须这么写
a)main函数的公式:
int main(int argc, char *argv[])
或者:
int main(int argc, char **argv)
不要写:int main(void)
b)切记:只要在命令行终端(不管是什么命令行)输入的任何内容,计算机都当成字符串处理
例如:./helloworld 100 200 ->实际计算机存储他们都是按照字符串存储
内存中最终存储字符串"./helloworld",字符串"100",字符串"200"
c)问:main函数的argc,argv到底是什么呢?
答:argc, argv功能是当运行程序时,可以在命令行终端上给程序传递数值
实际上输入的数值都是按照字符串处理了
argc:操作系统会帮你记录命令行输入的命令参数个数
argv:操作系统会用字符指针数组记录命令行输入的字符串内容
例如:启动helloworld程序,并且后面跟两个数字100和200
运行命令:./helloworld 100 200
结果是:
argc = 3
argv[0] = "./helloworld" //argv[0]指针指向字符串"./helloworld"
argv[1] = "100" //argv[1]指向字符串"100"
argv[2] = "200" //argv[1]指向字符串"200"
d)问:运行程序时,可以跟数字,但是数字最终都是以argv字符串形式存在,程序如何将一个字符串转对应的数字呢
例如:"100" 转成 100, "200"转200
答:利用函数:strtoul
函数声明如下:
unsigned long int strtoul(const char *str, char **endptr, int base)
函数功能:将一个数字的字符串形式转成数字形式
例如:"100" 转成 100, "200"转200
形参:
str:传递要转换的字符串的首地址,例如:"100"
endptr:一律给NULL
base:指定按那个进制进行转换
1.如果给0,根据实际的数字书写形式进行转换:
例如:“100”-> 100 //10进制
"0100"->0100 //8进制
"0x100"->0x100 //16进制
2.如果给16,强制转换成16进制
例如:"100"->0x64
3.如果给8,强制转换成8进制
例如:"100" ->0144
返回值:返回转换以后的数字
例如:argv[1] = "100"
int a = strtoul(argv[1], NULL, 0);
printf("a = %d\n", a); //a = 100
四、标准库函数(malloc和free)
4.1.回顾:C语言分配内存方法三种:
1.定义变量:缺陷:不能分配大量内存
2.定义数组 : 缺陷:跟数据类型相关,类型相同,作用域一说,可以做到大量内存分配局部数组:访问的范围有限
全局数组:存在乱序访问的问题,只能添加互斥保护,而保护会降低运行效率
3.结构体(联合体):可以有数组成员来实现大量内存访问,同样由数组的缺陷问题
问:如何做到大量分配内存,并且分配的内存跟数据类型无关,并且没有全局和局部作用域的限制(想用内存了,随时随地分配,不用内存了,随时随地释放)
答:利用malloc和free函数
4.2.详解malloc和free函数
a)malloc函数的原型:
void *malloc(unsigned long size);
功能:动态(随时随地的)连续分配内存,它分配的内存,只要不调用free函数就不会释放,并且分配的内存数据都是随机数
形参:
size:指定要分配的内存大小,单位是字节
返回值:返回分配的内存的首地址,注意要做强制类型转换
如果分配内存失败,返回NULL
头文件:为了使用此函数必须添加头文件:#include <stdlib.h>
参考代码:
int *p = (int *)malloc(8); //p指向连续分配的8个字节内存的首地址
if(NULL == p) {
printf("分配内存失败");
return -1;
} else {
printf("分配内存成功.\n");
*(p+0) = 2;
*(p+1) = 3;
printf("%d %d\n", *p, *(p+1));
}
或者:
void *p = malloc(8); //p指向连续分配的8个字节内存的首地址
if(NULL == p) {
printf("分配内存失败");
return -1;
} else {
printf("分配内存成功.\n");
*(int *)(p+0) = 2;
*(int *)(p+4) = 3;
printf("%d %d\n", *(int *)(p+0), *(int *)(p+4));
}
b)free函数原型
void free(void *p);
功能:调用此函数即可随时释放malloc分配的内存,将内存资源归还给操作系统
形参:
p:传递malloc分配的内存的首地址
参考代码:
//释放上面malloc分配的内存
free(p);
p = NULL; //务必养成好习惯,否则p就成野指针了
c)memset函数原型:
void memset(void *p, int data, int len);
函数功能: 设置内存数据
参数:
p:传递要设置的内存的首地址
data:传递要指定的数据
len:传递要设置的内存大小
例如: memset(p, 0, 8); //将以p为其实的内存,连续8字节都设置为0
五、文件操作函数
5.1.文件操作的标准库函数如下: fopen/fclose/fread/fwrite/fseek
a)fopen函数原型:
FILE*fopen(const char *filename, const char *mode);
功能:打开文件
参数∶
filename:指定要打开的文件名.建议绝对路径
mode:指定打开文件时的方式
r 以只读方式打开文件,该文件必须存在。
r+ 以读/写方式打开文件,该文件必须存在。
w 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则
写入的数据会被加到文件尾后
a+ 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在
,则写入的数据会被加到文件尾后
b∶跟以上方式联合使用,例如rb,rb+,wb,wb+,ab,ab+,表示以二进制的形式访问文
件
b)fclose函数原型:
函数原型: int fclose(FILE*fp );
功能︰关闭文件
参数:
fp:传递打开的文件对应的FILE指针
返回值︰成功返回0,失败返回-1
c)fwrite函数
unsigned long fwrite(const void *ptr, unsigned long size,unsigned long nmemb, FILE*stream)
函数功能∶向文件写入数据
参数:
ptr:要传递保存向文件写入数据的内存的首地址
size:指定要写入的单个数据块的大小,例如∶单个数据块大小=4字节
nmemb:指定要写入数据块的个数,例如∶数据块的个数为3.结果总共写12字节
stream:传递文件指针
返回值:写入失败返回-1,写入成功返回实际写入的数据的个数
d)fread函数
unsigned long fread( void *buffer, unsigned long size,unsigned long count, FILE*stream );
函数功能∶从文件读取数据,从硬件上将数据读取到内存中保存起来
buffer :指定保存读取数据的内存首地址
size:指定要读取的单个数据块大小
count:指定要读取的数据块的个数
fp:文件指针
e)rewind函数
void rewind(FILE*fp)
功能:将文件指针定位到文件开头
fp:文件指针
f)fseek函数
int fseek(FILE*fp, long offset, int fromwhere);函数功能∶定位文件指针(定位游标)
参数:
fp:文件指针,游标
offset:从文件的哪个地址开始
fromwhere:需要指定以下宏
SEEK_SET;
SEEK_CUR;
SEEK_END;