目录
前言
上周完成了C基础的课程,接下来就进入了【Linux系统和C高级阶段】,这周主要是接着接着C基础部分的,讲了一些更深一点的东西:接着函数部分,讲了函数指针、指针函数、函数指针数组和指向函数指针数组的指针,其中前三个是比较重要的,后面两个就属于套娃了,了解能看懂即可;再就是讲了typedef关键字、分文件编程。期间还针对比较难懂的指针,做了针对性的复习;然后讲了存储类型、枚举(enum)、结构体和共用体。
因为周五,上午时间复习,下午时间是阶段性考试,所以这周四天的时间里,讲的东西还是挺多的,而且也都是重点,因此这次复习总结,主要会针对函数和指针和数组的结合、结构体等几个方向,进行重点复习。
同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!
周一
一、函数
1.1 指针函数
本质是一个函数,返回值是一个指针类型。(后面是什么,就是什么)
格式:
返回值类型 *函数名(形参1类型 形参1名, 形参2类型 形参2名 ){}
注意:
指针函数不能返回局部变量的地址。
可以返回:
1.全局变量的地址
2.可以返回 static 修饰的局部变量的地址
3.也可返回由参数传递过来的地址 (例如 :strcpy strcat 等)
1.2 函数指针
本质是一个指针,指向一个函数。
格式:
返回值类型 (*函数指针名)(函数的形参列表);
典型使用场景:
定义一个函数指针,指向一个函数,函数名就是函数的首地址,函数指针指向函数以后,就可以通过函数指针来调用函数了。
回调函数:
#include <stdio.h>
int my_add(int x, int y){
return x+y;
}
int my_sub(int x, int y){
return x-y;
}
//所谓的回调函数,是将函数指针最为某个函数的形参
//在函数中通过函数指针调用函数时,具体调用的是哪个函数
//取决与 jisuan 这个函数的调用者给他传递的第三个参数
//传的是什么,就回过头去调用什么 所以叫做 回调函数
int jisuan(int x, int y, int (*p)(int, int)){
int temp = p(x,y);
return temp;
}
int main(int argc, const char *argv[])
{
int a = 20;
int b = 10;
printf("jisuan(a, b, my_add) = %d\n", jisuan(a, b, my_add));//30
printf("jisuan(a, b, my_sub) = %d\n", jisuan(a, b, my_sub));//10
return 0;
}
1.3 函数指针数组(了解)
本质是一个数组,数组中的每个元素都是一个函数指针。
格式:
返回值类型 (*函数指针数组名[数组长度])(函数的形参表);
1.4 指向函数指针数组的指针(了解)
本质是一个指针,指向一个函数指针数组。
格式:
返回值类型 (*(*指针名))(函数的形参表);
二、typedef
2.1 typedef的使用
本质上是用来给类型起别名的。
可以这么理解:原本的名字很长很复杂,可以简化的起一个简称或者绰号,这个简称或者绰号和和它的大名代表的意思是一模一样的。
例如:
typedef unsigned int m;//m本来是变量名,加上typedef之后就变成了新的类型名
//使用m定义的变量 和 使用unsigned int定义的变量是一样的
unsigned int v1 = 0;
m v2 = 0;
2.2 typedef和define的区别
1.宏定义是在预处理阶段完成替换的,typedef是类型的重定义,会在编译阶段检查;
2.宏定义只是简单的替换(无脑替换),typedef是类型的重定义;
3.typedef 指定后面必须要加分号,而define 不强制要求;
三、分文件编程
实际开发的过程中,会根据函数的功能不同,按照模块,分成多个文件处理。
.c 叫做源文件 .h 叫做头文件
.c 放的是函数的定义 .h 中放的是函数的声明
编译时 需要 gcc 后面加上所有的 .c 文件
简单归纳理解:程序篇幅过大,为了方便数理逻辑,main.c里放的都是函数调用;其他.c文件中放的是功能实现,.h文件里放的是函数声明。
注意事项:
1、.c文件中调用自己的头文件要用引号,不在同一目录下,要指明头文件所在的目录;
2、防止头文件重复包含,一般要在.h文件中加如下代码:
#ifndef __FILI_H__ //防止头文件重复包含的 __FILI_H__
#define __FILI_H__ //注意下划线和大小写;
#endif
周二
今天老师根据我们的学习情况,做了针对指针的复习。
一、指针复习
使用指针的三步骤:
1.定义指针变量
2.明确指针的指向(这一步必不可少)
3.操作指针指向的空间
1.1 一级指针
char *p;
//*p = 'H'; //错误的写法!! 指针的指向还不明确呢
int *q;
char value = 'N';
p = &value;
p++; //正确的 指针的指向向后偏移 1个char
*p++; //相当于 取 *p 的值 然后 p++
(*p)++; //相当于 value++, p的指向没有变
++(*p); //相当于 ++value, p的指向没有变
1.2 二级指针
int a = 10;
int *p = &a;
int **q = &p;
q++; //让 q 保存 p 后面一个 int * 大小的空间的首地址
*q++; //相当于 取出 *q 的值(也就是p的值) 然后 q++
(*q)++; //相当于 p++
p++; //让 p 指向 a 后面一个int 大小的空间的首地址
*p++; //相当于 取 *p 的值(也就是a的值) 然后 p++
(*p)++; //相当于 a++
1.3 指针和一维数组
格式:
char arr[] = "hello world";
char *p = arr;
等价关系:
arr[i] <==> *(arr+i) <==> p[i] <==> *(p+i)
区别:
arr是常量 p是变量
练习:
char *p = "helloworld";
char a[] = "helloworld";
char *str;
char b[32];
p++; //正确的 p的指向后移一个char
*p++; //正确的 相当于 先取*p 然后p++
(*p)++; //错误的 常量区的内容不允许修改
*p = 'H'; //错误的 常量区的内容不允许修改
p = "hqyj"; //正确的 p的指向可以改变
a++; //错误的 a是数组名 是常量 不能++
*a++; //错误的 a不能++
(*a)++; //正确的 相当于 a[0]++
*a = 'H'; //正确的 相当于 a[0] = 'H';
a = "hqyj"; //错误的 a是数组名 是常量 不能被赋值
str++; //正确的 指针str的指向 向后偏移一个 char
*str = 'H'; //错误的 str是野指针 不能对不确定的空间赋值
str = p; //正确的 指针变量的相互赋值
*str = 'H'; //错误的 常量区的内容不允许修改
b = a; //错误的 b也是数组名 是常量 不能被赋值
//数组一旦定义好了就不能整体赋值了
1.4 指针数组
本质是一个数组,数组中每个元素都是一个指针。
char *s[4] = {"./a.out", "hello", "world", "beijing"};
//s是一个指针数组 每个元素都是一个 char * 类型的指针
//s[0]指向 "./a.out"
//s[1]指向 "hello"
//s[2]指向 "world"
//s[3]指向 "beijing"
printf("%s\n", s[1]);//hello
printf("%s\n", *(s+2));//world
printf("%c\n", *(*(s+3)+3));//'j'
printf("%c\n", s[3][3]); //'j'
1.5 数组指针
本质是一个指针,指向一个二维数组,也叫行指针。
int s[2][3] = {1,2,3,4,5,6};
int (*p)[3] = s;//定义了一个行指针p指向 二维数组s
等价关系:
s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==
==> p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)
区别:
s 是常量 p 是变量
p++; //p的操作空间是 3个int 所以p++ 相当于p偏移了 12个字节
*p+1; //*p 操作空间是 1个int 所以 *p+1 是 第0行第1个元素的地址
*(*p+1);//第0行第1个元素
二、总结
根据老师课上讲的内容,加上我在csdn上找的一位大神对指针的理解的文章,结合起来还是比较好理解的:
- int p; //这是一个普通的整型变量
- int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
- int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
- int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
- int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
- int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
- int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
- Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,