目录
十二、指针和函数的关系
12.1 指针作为函数的参数
12.1.1 复制传参 -- 传输值
12.1.2 地址传参 -- 传地址
12.1.3 传数组
12.1.4 指针函数:指针 作为函数的返回值
12.1.5 函数指针:指针保存函数地址
十三、经常容易混淆的指针
13.1 第一组
①、 int *a[10];
②、int (*a)[10];
③、int **p;
13.2 第二组
①、 int *f(void);
②、int (*f)(void);
十四、特殊指针
14.1 空类型指针 (void *)
14.2 NULL
十五、main函数传参
可以给函数传一个整型、字符型、浮点型的数据,也可以给函数传一个地址。
函数的传参方式 :
#include
//函数的传参方式之复制传参:将实参的值传递给形参,不管形参怎么改变,都和实参没有关系
void fun1(int a, int b)
{
int temp;
temp = a;
a = b;
b =temp;
printf("in fun: a = %d, b = %d\n", a, b);
printf("in fun: &a = %d, &b = %d\n", &a, &b);
}
void test1()
{
int a =199, b = 20;
printf("before fun: a = %d, b = %d\n", a, b);
printf("before fun: &a = %d, &b = %d\n", &a, &b);
fun1(a, b);
printf("after fun: a = %d, b = %d\n", a, b);
}
void main()
{
test1();
}
函数的传参方式之地址传参:将实参的地址传递给形参,
//形参对保存的地址内容进行任何操作,实参的值也会跟着改变
void fun2(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b =temp;
printf("in fun: *a = %d, *b = %d\n", *a, *b);
printf("in fun: a = %d, b = %d\n", a, b);
}
void test1()
{
int a =199, b = 20;
printf("before fun: a = %d, b = %d\n", a, b);
printf("before fun: &a = %d, &b = %d\n", &a, &b);
fun2(&a, &b);
printf("after fun: a = %d, b = %d\n", a, b);
}
void main()
{
test1();
}
注意:
void fun(char *p) { p = "hello ?"; } void main() { char *p = "how are you ?"; fun(p); printf("%s\n", p); }
运行结果还是 “how are you ?”,
原因:因为原本实参就是一个一级指针,传进去还是一个一级指针,本质是复制传参
改变为地址传参
void fun(char **p) { *p = "hello ?"; } void main() { char *p = "how are you ?"; fun(&p); printf("%s\n", p); }
总结:要想改变主调函数中变量的值,必须传变量的地址,而且还得通过 * + 地址 去赋值 无论这个变量是什么类型的
注意:
如果实参是一个普通变量,地址传参的话就需要形参是一级指针,
如果实参是一个一级指针,地址传参的话就需要形参是一个二级指针,
以此类推.......
将数组作为参数传递给函数,不存在复制传参和地址传参,本质都是地址传参,所以在函数内部对数组进行改变,则函数执行完毕后,原本的数组也会改变,因为传递给函数的都是数组的地址。
给函数传数组时,没法将数组内容作为整体一下 传进去,只能传数组的地址。
#include
//传一维数组
//void fun1(int p[])//形式1
void fun1(int *p) //形式2(常用)
{
printf("fun1: p[2]= %d\n",p[2]);
printf("fun1: *(p + 3) = %d\n", *(p + 3));
}
void test1()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9,};
fun1(a);
}
//传二维数组
//void fun1(int p[][4])//形式1
void fun2(int (*p)[4])//形式2:通过数组指针
{
//p[x][y] <== > *(*(p + x) + y )
printf("in fun2: p[0][2]=%d\n", p[0][2]);
printf("in fun2: *(*(p + 1) + 2) = %d\n", *(*(p + 1) + 2));
}
void test2()
{
int a[2][4] = {1, 2, 3, 4,
5, 6, 7, 8};
fun2(a);
}
//传指针数组
void fun3(char **q)
{
int i;
for(i = 0; i < 3; i++)
{
printf("in fun3: %s \n", q[i]);
}
}
void test3()
{
char *p[3] = {"hell0", "world", "xiao"};
fun3(p);
}
void main()
{
test1();
test2();
test3();
}
指针函数本质是一个函数,只不过函数的返回值是一个指针
一个函数可以返回整型数据、字符数据、浮点型的数据,也可以返回一个指针
//指针函数
char *fun4()
{
char str[100] = "hello world!";
return str;
}
void test4()
{
char *p;
p = fun4();
printf("p = %s\n", p);
}
void main()
{
test4();
}
原因:如果在一个函数内部定义了一个变量,如果不加任何修饰当前这个数组应该存储在内存中的栈区,栈区特点——会随着代码断结束而释放空间,也就意味着当当前函数执行完毕后,str这个空间就释放了,一旦释放该函数就不可在返回值了,最多返回是str这个地址,但地址里面值就不一定是现在值。
//指针函数
char *fun4()
{
//栈区开辟空间会随着当前代码段结束而释放空间
//char str[100] = "hello world!";
//静态区空间不会随着当前代码段的结束而释放空间
static char str[100] = "hello world!";
return str;
}
void test4()
{
char *p;
p = fun4();
printf("p = %s\n", p);
}
void main()
{
test4();
}
修改:
定义的函数,在运行程序是,会将函数指令加载到内存的代码段,所以函数也有起始地址。
c规定:函数名字就是函数的首地址,即函数入口地址,这样就可以定义一个指针变量,来存放函数的地址。—— 这个指针变量就是函数指针变量、
返回值类型 (*函数指针变量名)(形参列表);
int (*p)(int, int); //定义一个函数指针变量p,p指向的函数
//必须有一个整型返回值,有2个整型参数。
int max(int x, int y)
{
}
int min(int x, int y)
{
}
可以用这个p存放这类函数地址
p = max;
p = min;
①、 通过函数名去调用函数(最常用)
int max(int x, int y)
{
}
int main()
{
int num;
num = max(3,5);
}
②、通过函数指针变量去调用
int max(int x, int y)
{
}
int main
{
int num;
int(*p)(int, int);
p = max;
num = (*p)(3, 5); //num = p(3, 5);
}
③、函数指针数组
函数指针数组:本质是一个数组,数组里面的每一个元素都是一个函数 指针
返回值类型 (*函数指针变量名[函数指针的个数])(形参列表);
int (*p[10])(int, int)
定义一个函数指针数组,有10个元素 p[0] -- p[9]每个元素都是函数指针变量,指向函数
必须有整型的返回值,2个整型参数
④、函数指针最常用的地方
函数指针最常用地方在于将一个函数作为参数传递给另一个函数的时候要使用函数指针
将一个函数作为参数传递给另外一个函数,将这个函数称之为回调函数
#include
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mux(int x, int y)
{
return x * y;
}
int dive(int x, int y)
{
return x / y;
}
//可能以上函数是封装好的,但函数的2个参数值没有先知道,
//这样情况就可以采用回调函数
int process(int (*p)(int, int), int a, int b)
{
int ret;
ret = (*p)(a, b);
return ret;
}
void main()
{
int num;
num = process(add, 2 ,3);
printf("num = %d\n",num);
num = process(sub, 2 ,3);
printf("num = %d\n",num);
num = process(mux, 2 ,3);
printf("num = %d\n",num);
num = process(dive, 2 ,3);
printf("num = %d\n",num);
}
int *a[10];
是指针数组,数组a中有10个整型的指针变量
a[0 ]- a[9]
其一般拿来保存多个字符串
int (*a)[10];
数组指针变量,是指针变量。占4个字节,村地址编号。
指向一个数组,加1,指向下个数组。
作用1:数组指针 《==》 相当于为了定义一个行指针,可以保存二维数组的首地址
作用二:将一个二维数组作为函数的参数传递进去,这个参数一般定义为数组指针。
int **p;
这是个指针的指针,保存指针变量的地址。保存一级指针地址
常见用法1:
int **p;
int *q;
p = &q;
常见用法2:
int **p;
int *q[10];
分析:q是指针数组的名字,是指针数组的首地址,是q[0]的地址,
q[0]是个int *类型的指针,
所以q[0]指针变量的地址,是int**类型
int *f(void);
本质:是一个函数,返回值是指针
注意:*f没有用括号括起来
它是函数的声明,声明这个函数的返回值为int *类型
int (*f)(void);
主要是为了保存函数的首地址,可通过函数指针变量来替换我们的函数名来使用。
作用:可以把一个函数作为参数传递给另外一个函数的时候,就需要采用函数指针。
注意:*f用括号括起来,*修饰f说明,f是个指针变量,f是个函数指针变量,存放函数的地址,它指向的函数,必须有一个int型的返回值,没有参数。
char * 类型的指针指向char型的数据
int * 类型的指针指向int 型的数据
float* 类型的指针指向float 型的数据
void *指向void型的数据?——NO, 因为没有void类型的变量,
对应类型的指针只能存放对应类型的数据地址
void * 通用指针,任何类型的指针都可以给void*类型的指针变量赋值。主要也是用在函数的参数和返回值的位置
int *p;
void *q;
q = p; 这样可以不用强制转换
举例子:
void * memset(void *s,int c,size_ _t n);
函数的功能是将 s 指向的内存前 n 个字节,全部赋值为 c 。
Memset可以设置字符数组、整型数组、浮点型数组的内容,所以第一个参数,就必须是个通用指针
它的返回值是s指向的内存的首地址,可能是不同类型的地址。所以返回值也得是通用指针
注意: void*类型 的指针变量,也是个指针变量,在32为系统下,占4个字节
空指针:给一个指针变量初始化
char *p = NULL; //定义指针变量没有赋初值,一般定义NULL,
#include
int main(int argc, char *argv[])
{
int i;
printf("argc = %d\n", argc);
for(i = 0; i < argc; i++)
{
printd("argv[%d] = %s\n", i, argv[i]);
}
}
int main(int argc, char *argv[])
argc:是一个int类型的变量,标识命令终端传入的参数的个数
argv:是一个指针数组,用于保存每一个命令终端传入的参数