1. 指针
内存单元地址即为指针
指针变量是存放地址的变量
指针变量的定义
int *p; // *:说明p是指针变量,int 说明p中存放的地址是int类型
指针所占内存空间的大小:
- 一个字节有8位二进制
- 在32位系统中一个地址占4个字节,和地址类型无关,和谁的地址都无关,所以一个指针变量是4个字节;64位系统,指针变量是8个字节
两个指针变量之间赋值,就是修改指向
#include <stdio.h>
// 实参初始化形参 int* m = p int* n = q;
//函数内部三行执行完,m指向q,n指向p
void fun(int* m,int* n)
{
int* temp = m;
m = n;
n = temp;
}
int main()
{
int a = 3, b = 5;
int* p = &a, q = &b;
fun(p,q);
//函数中并没有写有关a b的值的语句,只是更改了指向,所以在此输出a b 的值并不变
printf("a is %d b is %d\n", a, b); // ? ? 3 5
return 0;
}
int *p
- 指针的类似是什么? int *
用手挡住 p就是指针的类型- 指针指向的类型是什么? 指向 int 类型
用手挡住*p就是指针指向的类型
1.1 指针指向数组
- 将数值的首元素地址赋值给指针,我们叫指针指向数组。
- 定义一个指针指向一个数组,指针的类型应该是数组的元素类型。
指针指向整型数组
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int* p;//想要定义指针指向数组a;那么指针的类似应与数组a元素的类型一致
p = a;// 等价于 p = &a[0]; 让p指向数组a
指针指向字符数组
char buf[] = “hello world”;
char* p = buf;
2. 指针数组
概念:是一个数组,只不过数组的里的每个元素都是指针
本质:数组
int a[5] = {1,2,3,4,5};
- 数组元素的类型是什么? int类型
- 数组的元素个数是几个? 5个
- 数组名就是数组的首地址。数组名是常量,是数组的元素类型的地址。
- 数组中的元素在内存中连续存放。已知数组首地址,可以通过地址的偏移运算得到数组中的每个元素的地址。
&a[i]和a+i等价 //a+i 是数组a中i元素的地址
a[i]和(a+i)等价* //*间接运算,得到数组a中i元素本身
整型数组,数组的元素类型是整型
字符数组,数组的元素类型是字符
指针数组,数组的元素类型是指针
作用:当想要一次性定义多个相同类型的指针的时候需要用指针数组。
语法: 定义指针数组
数据类型* 数组名[元素个数]
整型指针数组 int* p[3] -------- | int * | int * |int * |
字符指针数组 char* name[3] ------- | char * | char * | char * |
2.1 整型指针数组
初始化整型指针数组
方式一:
int m = 3, n = 5, k = 7;
int* p[3];
p[0] = &m;
p[1] = &n;
p[2] = &k;
方式二:
int m = 3, n = 5, k = 7;
int* p[3] = {&m,&n,&k}; //元素个数是3 元素类型是 int *
2.2 字符指针数组
定义字符指针数组:
char * name[5] = {“zhangsan”,“xiaosi”,“asan”,“xiaoming”,“xixi”};
// 定义了一个字符指针数组,元素个数是5, 每个元素的类型都是char*, 存放的是每个字符的首地址
name的类型是字符指针数组
name[0]的类型 是 char*
sizeof(name[0])-------> 4
sizeof(name)-------> 20 5*4
%s 需要的参数是 字符类型的地址,打印字符串只需要首地址就可打印
3. 数组指针
概念:指向数组的指针
本质:指针
作用:传递二维数组,将二维数组作为函数的参数传递
整型指针,指向的类型是整型
字符指针,指向的类型是字符
数组指针,指向的类型是数组
定义一个数组:
int a[3][4];
//二维数组是由3个int[4]
int (* p)[4] = a;
指针的类型是:int (*) [4]
指针指向的类型是: int [4]
sizeof( p )== 8 //64位
#include <stdio.h>
void showArr(int (*p)[4], int row)
{
int i,j;
for(i = 0; i < row; i++)
{
for(j = 0; j < 4; j++)
{
//p[i][j] 可以替换成*(*(p+i)+j)
printf("%d",p[i][j]);
}
printf("\n");
}
printf("%d \n",sizeof(p));
}
int main(int argc, const char *argv[])
{
int a[3][4] = {{11,22,33,44},{55,66,77,88},{12,34,56,78}};
//打印二维数组中所有元素,遍历
showArr(a,3);
return 0;
}
3.1 行指针和列指针
此处的行指针和列指针并不是C语言里面的指针的标准概念(内存单元地址)
比如:
int a[5] = {1,2,3,4,5};
int i = 2; //i是整型,i是几,就对应的下标位几的元素,此时把i可以看出指针(非C语言意义的指针),指向每个元素
printf(“%d\n”,a[i]);
//i 称为行指针
//j 称为列指针
#include <stdio.h>
int main()
{
int a[3][4] = {{1,2,3,4},{3,3,3,3},{7,8,9,10}};
int (*p)[4] = a;//定义一个数组指针,保存二维数组的首地址
int i,j;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
//p[i] == a[i] == *(p+i)
//p[i][j] == a[i][j] == *(*(p+i)+j)
printf("%d ",*(*(p+i)+j));
}
printf("\n");
}
return 0;
}
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
a[0][0] a[0][1] a[0][2] a[0][3] // 第0行一维数组的名字 a[0]
a[1][0] a[1][1] a[1][2] a[1][3] // 第1行一维数组的名字 a[1]
a[2][0] a[2][1] a[2][2] a[2][3] // 第2行一维数组的名字 a[2]
int b[5] = {1,2,3,4}; //a[i]==*(a+i)
*(b+0) == b[0]
*(b+1) == b[1]
....
*(b+i)==b[i]
a[0][0] a[0][1] a[0][2] a[0][3] // 第0行一维数组的名字a[0]
*(a[0] + 0) --->a[0][0]
*(a[0] + 1) --->a[0][1]
*(a[0] + j) ---> a[0][j]
*(a[i] + j) ---> a[0][j]
将a[i]替换成*(a+i)
*(*(a+i) + j) == a[i][j]
区分数组指针和指针数组
int (*p)[4]; //定义的是数组指针
int * p[4]; //定义的是指针数组
4. 二级指针
二级指针:指向指针的指针,对一级指针取地址就是二级指针
4.1 定义及使用
定义整型变量:int a = 10;
定义整型指针变量: int *p = &a;
定义二级整型指针变量: int **q = &p;
修改变量的值的方式:
- 直接修改:a = 200;
- 间接修改: *p = 200; **q = 200;
a==*p==**q ------> int
&a== p==*q -------> int *
&p==q ----------------> int **
&q ---------------------> int ***
例题
请问下面的程序有问题吗?如果有请指正
#include <stdio.h>
#include <stdlib.h>
void get_memory(int *q)
{
q = malloc(10 * sizeof(int));
}
int main()
{
int i;
int *p = NULL;
get_memory(p);
for(i = 0; i < 10; i++)
{
p[i] = i;
}
for(i = 0; i < 10; i++)
{
printf("%d\n", p[i]);
}
}
get_memory§是值传递,将p的值NULL传给q,q就变为NULL
在执行for语句就会出现段错误,因为p的值位NULL
所以要修改为地址传递,malloc申请的首地址传给了p
参数上地址传递修改代码:
#include <stdio.h>
#include <stdlib.h>
void get_memory(int **q)
{
*q = malloc(10*sizeof(int));
if(*q == NULL)
{
printf("malloc failed\n");
return;//申请失败返回空
}
}
int main(int argc, const char *argv[])
{
int *p = NULL;
get_memory(&p);
int i;
for(i = 0; i < 10; i++)
{
p[i] = i;
}
for(i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
free(p);
return 0;
}
通过返回值把堆空间的首地址返回来:
#include <stdio.h>
#include <stdlib.h>
int *get_memory()
{
int *q = malloc(10*sizeof(int));
if(q == NULL)
{
printf("malloc failed\n");
return q;//申请失败返回空
}
}
int main(int argc, const char *argv[])
{
int *p = NULL;
p = get_memory(); //返回值形式
int i;
for(i = 0; i < 10; i++)
{
p[i] = i;
}
for(i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
free(p);
return 0;
}
5. 指针函数
本质:是函数
函数返回值类型是指针就叫做指针函数
返回值类型:int* char* double* … struct student* void*(无指向的指针类型)
5.1 指针函数举例
//下面的函数都是指针函数
int* get_memory() //返回值是int* ----指针函数
void* malloc(int size) //返回值是void* ----指针函数
char *strcpy(char *dest,const char src) //返回值是char ----指针函数
char *strcat(char *dest,const char src) //返回值是char ----指针函数
请编写一个函数,查找一个字符串中是否包含某个字符,如果存在请将第一次出现的字符的首地址返回,不存在返回NULL
“hello” ‘e’
#include <stdio.h>
#include <stdlib.h>
char *iswithin(char *str,char zi)
{
int i;
for(i = 0; str[i]!='\0'; i++)
{
if(str[i] == zi)
{
//找到了位置
return &str[i];//return str +i; &a==&p==a+i==p+i
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
char *result = iswithin("hello",'s');
if(result == NULL)
{
printf("no \n");
}
else
{
printf("yes \n");
}
return 0;
}
6. 函数指针
概念:指向函数的指针 --存地址–函数的地址
作用:把函数作为参数传递,实现多态的效果,增加灵活性和可扩展性
整型指针,指向的类型是整型
字符指针,指向的类型是字符
数组指针,指向的类型是数组
函数指针,指向的类型是函数
6.1 函数类型
函数的类型就是将函数名和形参名字去掉
请说出下面函数的类型
void setA(int *p) //void (int *)
void setStudentInfo(struct student *p,int n) //void (struct student *,int )
int getMax(int a,int b) // int (int,int)
char *strcpy(char *dest,const char *src) //char * (char *,char *)
6.2 定义函数指针指向函数
复制函数声明,将函数名替换成(*p),再去掉形参名字
定义函数指针指向函数,需要:
- 要指向哪个函数
- 这个函数的类型是什么
- 函数指针指向这个函数
如何定义一个函数指针,指向一个函数
void setA(int *p) // 函数的类型:void (int *)
void (*p)(int *) //定义函数指针,复制函数声明,将函数的名字,替换成(*p)
p = setA; //定义一个函数指针变量p,给函数指针变量p赋值,函数的名字就是函数在内存当中的首地址
void (*p)(int *) = setA; //在定义的同时,给函数指针变量赋值
#include <stdio.h>
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 mod(int a,int b)
{
return a%b;
}
int main(int argc, const char *argv[])
{
// add sub mul mod 四个函数的类似是一样的 int (int,int)
//1. 定义一个函数指针变量,名字叫p
int (*p)(int,int);
//2.给函数指针变量赋值,函数的名字就是函数的首地址
p = add; //p指向了add函数
int result = add(3,5);
printf("result is %d\n",result);
//3.通过函数指针来调用函数,直接将指针变量p,当做函数的名字来使用,进行调用函数
result = p(3,5); //把p当做函数的名字来用了
printf("result is %d\n",result);
p = mul;
result = p(3,5); //把p当做函数的名字来用了
printf("result is %d\n",result);
//p里面装的是哪个函数的地址,在调用函数的时候,调用的就是哪个函数
return 0;
}
6.3 回调函数
本质:就是函数,函数的参数是函数指针变量的函数,称为回调函数
回调函数的函数参数类型只要有一个参数类型是函数指针就是回调函数。
#include <stdio.h>
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 mod(int a,int b)
{
return a%b;
}
//回调函数
/*
函数参数列表
第1参数:函数指针变量 int (*p)(int,int)
第2,3参数:int a,int b
为了增肌函数功能的灵活性,在通过函数指针调用函数的时候,需要两个int 类型的参数
*/
int process( int (*p)(int,int), int a, int b)
{
int res = p(a,b);
return res;
}
int main(int argc, const char *argv[])
{
int result = process(add,3,5);
printf("result is %d\n",result);
result = process(sub,3,5);
printf("result is %d\n",result);
return 0;
}
7. 函数指针数组
本质是数组:数组中的每个元素都是函数指针
作用:一次性定义多个相同类型函数指针变量
7.1 函数指针数组的定义
//定义一个数组,存上4个int*(int,int)的变量
int(* a[4])(int, int); //[]优先级高于* a先和[]结合,就确定本质是数组,一次性定义4个函数指针变量
//为数组的每一个元素赋值
a[0] = add;
a[1] = sub;
a[2] = mod;
a[3] = mul;
最标准写法:
int(* a[4])(int, int) = {add, sub, mod, mul};
数组元素类型: 挡住数组名和[4],剩下的就是元素类型
#include <stdio.h>
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 mod(int a,int b)
{
return a%b;
}
//回调函数
/*
函数参数列表
第1参数:函数指针变量 int (*p)(int,int)
第2,3参数:int a,int b
为了增肌函数功能的灵活性,在通过函数指针调用函数的时候,需要两个int 类型的参数
*/
int process( int (*p)(int,int), int a, int b)
{
int res = p(a,b);
return res;
}
int main(int argc, const char *argv[])
{
int(* a[4])(int, int) = {add, sub, mod, mul};
int i;
for(i = 0; i < 4; i++)
{
printf("res is %d\n",process(a[i],10,5));
}
return 0;
}
总结
先确定本质,有了本质就知道用手挡住谁
- 见到(*p) 的语法,立刻本质定义为指针
- 如果是指针,挡住p,去找p的类型;挡住*p或者(*p)去找p指向的类型
- []优先级高于* ,来确定本质是数组
- 如果是数组,[]里面的数字就是元素的个数;挡住数组名和元素个数 ,剩下的就是元素的类型
- ()优先级高于*,来确定本质是函数
如果是函数,用手挡住函数的名字,剩下的就是函数的类型;
()内是参数个数和类型,()外返回值类型
指针指向数组 | 指针数组 | 数组指针 | 指针函数 | 函数指针 | 函数指针数组 | |
---|---|---|---|---|---|---|
本质 | 指针 | 数组 | 指针 | 函数 | 指针 | 数组 |
概念 | 就是一个指针变量 | 是一个数组,只不过数组的里的每个元素都是指针 | 指向数组的指针,为了传递二维数组,将二维数组作为函数的参数传递 | 函数返回值类型是指针就叫做指针函数 返回值类型:int* char* double* … struct student* void*(无指向的指针类型) | 指向函数的指针,为了把函数作为参数传递,实现多态的效果,增加灵活性和可扩展性 | 数组中的每个元素都是函数指针,为了一次性定义多个相同类型函数指针变量 |
定义 | int a[10] = {1,2,3,4,5,6,7,8,9,10}; int* p;//想要定义指针指向数组a;那么指针的类似应与数组a元素的类型一致 p = a;// 等价于 p = &a[0]; 让p指向数组a | 数据类型* 数组名[元素个数] eg:整型指针数组 int* p[3] | //定义一个数组: int a[3][4]; //二维数组是由3个int[4] int (*p)[4] = a; | int* get_memory() //返回值是int* | void setA(int *p) // 函数的类型:void (int *) void (*p)(int *) = setA; //根据函数类型定义,给函数指针变量赋值 | eg:int(* a[4])(int, int) = {add, sub, mod, mul}; |
(1)int p[3]; //本质数组 整型数组 3个元素, 元素类型int, 3个int类型的整型数组
(2)int (*p)[3]; //本质指针 数组指针 p的类型: int (*)[3], p指向类型 int [3],
//指向元素个数为3个int的一维数组类型的数组指针
(3) int* p[3]; //本质数组 指针数组 p的类型: int*,元素个数为3个int*的指针数组
(4)int * p (int ); //本质函数 指针函数 函数名字p,函数类型 int* (int)
(5)int (*p)(int); //本质指针 函数指针 p的类型: int (*)(int), p指向类型 int (int)
(6)int *(*p)(int); //本质指针 函数指针 p的类型: int *(*)(int), p指向类型 int *(int)
//是一个指向 指针函数 的 函数指针
(7)int (*p[3])(int); //本质数组 函数指针数组 3个元素, 元素类型int (*)(int),
//3个int (*)(int)类型的函数指针数组
(8)int *(*p[3])(int); //数组 函数指针数组 3个元素, 元素类型int *(*)(int)
//3个int *(*)(int)类型的函数指针数组
//元素类型是函数指针,指向的函数是指针函数 int* (int)
int *(*p[3])(int)
函数指针数组 : 3个都是 函数指针 -->指向的函数类型 int * (int )--->指针函数
这是一个函数指针数组 ,数组中每个元素都是函数指针 指向是一个指针函数