网站开发指什么软件,公司查名网站,什么浏览器可以进黄页zol问答,岳阳市建设局网站指针 1. 什么是指针#xff1f;2. 如何编址#xff08;即如何给地址分配空间呢#xff09;3. 概念和基本术语3.1指针的值指针所指向的地址/内存区3.2 指针的类型#xff08;指针本身的类型#xff09;思考#xff1a; 3.3 指针所指向的类型3.4 指针本身所占据的内存区3.5… 指针 1. 什么是指针2. 如何编址即如何给地址分配空间呢3. 概念和基本术语3.1指针的值指针所指向的地址/内存区3.2 指针的类型指针本身的类型思考 3.3 指针所指向的类型3.4 指针本身所占据的内存区3.5 指针数组和数组指针注意区分3.6 野指针 4. 指针运算5. 指针关系运算6. 二级指针7. 字符串与指针的应用8. 指针数组的应用9. 数组指针的应用10. 指针的传参11. 函数指针12. 函数指针数组13. 回调函数 1. 什么是指针
指针是一个32位数的一个数。只不过这个数是二进制表示的。例如0000 0000 0000 0000 … 000132个bit位。为了方便表示因此转换成16进制所表示的一个数。
0000 0000 0000 0000 0000 0000 0000 0000 32bit
--------- --------- 1 2 3 4 字节数内存区最小的存储单元是一个字节每一个存储单元都有其对应且唯一的编号。这个编号我们称为地址也叫指针。通过地址/指针就能找到某一块内存的首地址/某一个内存单元。
拿32位平台举例一个计算机的某一个内存区有2^32个存储单元。
指针即地址地址即指针。指针具体来讲就是一个值不过这个值是内存区的一个地址。
2. 如何编址即如何给地址分配空间呢
我们知道如果给每一个地址分配一个字节的空间。从32位二进制的第一个地址全是0开始到最后一个地址全是1一共有2^32个字节。
2^32 byte 4GB 2^32/1024/1024/1024 4GB
3. 概念和基本术语
3.1指针的值指针所指向的地址/内存区
指针的值是指针本身存储的数值而不是一个一般的数值这个值将被编译器当作一个地址。在32 位程序里所有类型的指针的值都是一个32 位整数因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始长度为sizeof(指针所指向的类型)的一片内存区。
以后我们说一个指针的值是0xFFFF就相当于说该指针指向了以0xFFFF 为首地址的一片内存区域
反过来一个指针指向了某块内存区域则该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。
以32位举例一个内存有32字节一共有2^32个内存单元一个字节是一个内存单元。
1 byte 8 bit3.2 指针的类型指针本身的类型
去掉指针名字
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]思考
为什么要搞不同类型的指针呢 为什么不能用一个通用的指针访问任何类型的变量呢 定义指针类型的意义是什么
1.指针类型决定了指针解引用的权限有多大不同类型的指针解引用的权限不同。比如int*类型的指针可以访问4个字节double *可以访问8个字节。而char *类型只能访问1个字节。
2.指针类型决定了指针的步长有多大。不同类型的指针步长不同比如int *类型的指针步长为4double *步长为8。而char *类型步长为1。 3.3 指针所指向的类型
去掉*和指针名字就是指针所指向的类型
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
3.4 指针本身所占据的内存区
指针本身占了多大的内存你只要用函数sizeof(指针的类型)测一下就知道了。指针是用来存放地址的所以存储地址有多大指针就有多大。在32 位平台里指针本身占据了4 个字节的长度。在64位平台里指针占据8个字节。指针本身占据的内存这个概念在判断一个指针表达式后面会解释是否是左值时很有用。
3.5 指针数组和数组指针注意区分
Ex1.int * p //p只是一个指针
Ex2.int *p[3] //p是存放整型指针的数组
Ex3.int (*p)[3] //p是指向整型数组的指针
原理如Ex3.*优先级小于[]因此若加括号的话表明是p优先与*结合,因此p是指针再与[]结合表明指针所指向的是一个数组。Ex2可举一反三。
指针数组的进阶玩法
//数组名本身就是一个地址而地址即指针
int main() {int arr[5] {1, 2, 3, 4, 5};int *p arr;//arr arr[0]printf(%d %d %d %d, arr[0], 1[arr], p[2], 3[p]); //arr[2] *(p2) *(2p) 2[p] 2[arr]}由此可见只要p指向数组名其实本质上就是数组的首地址赋值给了p这个指针变量,所以p和arr是等价的。那么结合[]就可以玩出“新花样”。重点是arr、如同p本质上都是指针(地址)。而[1]、[2]相当于这个指针向后移动了1位、2位。
下面这个内存变化图你看不懂你要是看不懂把上面文字重新读一遍吧。
3.6 野指针
定义不知道指向哪里的指针随机的地址。
什么样会导致野指针
指针未初始化若指针未初始化那么该指针会随机找一块内存区分配地址这个地址大概率不是计算机允许访问的区段。因此如果不知道指针一开始要访问的空间则置为NULL。指针越界访问
int main() {int arr[10] {0};int *p arr;for (int i 0; i 10; i) {//当i10时p访问的不是给数组分配的地址空间*p i;p;}return 0;
}**指针指向的空间已经被提前释放了。**比如前一秒指针在使用的时候申请了内存空间但是你用完之后把它释放了当你想要再去使用该指针访问这个空间时已经没有权限了。
int* test(){int a 10;//分配四个字节内存空间return a;//返回a的地址
}int main(){int *p test();//当test函数调用完a的地址对应的空间已经被释放了此时你再用p去访问这个空间就是非法访问。return 0;
}注意以下写法是错误的。
int main(){int *p NULL;*p 10;//空指针也叫空地址是允许读不允许写的会抛出写入访问权限异常return 0;
}思考一下怎么解决其实很简单加个判空条件即可。
因此所有的指针/地址无非就两类叫有效指针和空指针。
4. 指针运算
int main() {int arr[10] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0};int *p1 arr[0];int *p2 arr[9];printf(p1 %p,p2 %p\n, p1, p2);printf(%d, p2 - p1);return 0;
}打印出来的结果是9这是因为首指针到尾指针需要4*9个字节才能到达尾指针变量的首地址。以%d输出即9故指针-指针指针需要走多少步两个指针之间的元素个数
假设我们要自己写一个统计字符串长度的函数。
例1统计字符串长度
int main() {char str[] abcde;int len my_strlen(str);printf(%d, len);return 0;
}没学指针前的你可能会这么写
int my_strlen(char str[]) {int count 0;for (int i 0; str[i] ! \0; i) {count;}return count;
}学了指针后
int my_strlen(char *p) {int count 0;while (*p ! \0) {count;p;}return count;
}int my_strlen(char *p) {char *start p;while (*p ! \0)p;return p - start;
}这里传过去的str实际上是一个str中a的首地址本质上还是指针所以我们能通过一个*p来指向它。
5. 指针关系运算
例2将某数组元素全部置为1
#define N_VALUES 5int main() {int values[N_VALUES] {0};int *p values;for (p values[0]; p values[N_VALUES]; p)*p 1;for (int i 0; i 5; i)printf(%d , values[i]);return 0;
} 你会发现p values[0]、p可以省略因为指针一开始就初始化了p可以放到for里面。其实就是一个while循环。
int *p values;
while(p values[N_VALUES]) *p 1;或者
for (int *p values; p values[N_VALUES]; p)*p 1;6. 二级指针
int main() {int a 10;int *pa a;int * *ppa pa;printf(%d\n, *pa);printf(%d, **ppa); //*ppa pareturn 0;
}上述代码内存表示图 7. 字符串与指针的应用
例1
int main() {
// char *p hello world!;//存放字符串的指针pchar str[] hello world!;char *p hello world!//本质上是将字符串的首字符h的地址存放到p里面printf(%c\n, *p);//h printf(%s\n, str);//hello world!printf(%s\n, p);//hello world!return 0;
}例2
char *p1 hello;
char *p2 hello;
char str1[] hello;
char str2[] hello;
printf(%d,str1str2);//0
printf(%d,p1p2);//1为什么会这样呢 这是因为当我们用指针来声明一个字符串时
第一其实就是把字符串的首字符的地址存放到指针变量中因为一个字符占一个字节所以首字符的地址就是整个字符串的地址。
第二如果我们用一个指针去声明某个字符串时该字符串就是一个常量字符串。我们无法对这个常量字符串进行写操作。 上述代码内存表示图 例3
char *p1 hello;
p1 world;//非法操作因为p1是一个常量字符串
char str[] hello;
char *p2 str;
p2 world;//p2是一个指向str的一个指针变量修改p2就相当于修改str因此可行现在能理解为什么打印p是全部字符串而*p是首字符了吧
因为*p是取出字符串的首地址的内容即字符串的首字符的地址的内容。在例1中即h
而p无论是指向一个字符串还是自己声明一个常量字符串。它本质上都是将字符串赋值给这个指针变量p而已。
8. 指针数组的应用
如何玩转指针数组假设你已经知道什么是指针数组。我们尝试
例1
int main(){int a 1,b 2,c 3;int* ptr[] {a,b,c};for(int i0;i3;i){int* temp ptr[i];//拿到指针数组printf(%d ,*temp);//解引用每一个指针}return 0;
}例2
int main(){int arr1[] {1,2,3,4,5};int arr2[] {1,2,3,4,5};int arr3[] {1,2,3,4,5};int* ptr[] {arr1,arr2,arr3};//数组名本身就是地址/指针for(int i0;i3;i){int* temp_arr ptr[i];for(int j0;j5;j){printf(%d ,*(temp_arrj));//temp_arr(数组名)是地址数组名j就是当前数组第j个地址//temp_arr[j] *(temp_arrj) *(ptr[i]j) ptr[i][j]}printf(\n);}return 0;
}temp_arr[j] *(temp_arrj) *(ptr[i]j) ptr[i][j]//变形为二维数组由此不难发现其实一个指针数组可以模拟二维数组。
只不过对于以上我个人更喜欢这种方式
int main() {int arr1[] {1, 2, 3, 4, 5};int arr2[] {1, 2, 3, 4, 0};int arr3[] {1, 2, 3, 4, 3};int *ptr[] {arr1, arr2, arr3}; //数组名本身就是地址/指针for (int i 0; i 3; i) {int *temp_arr ptr[i];for (int j 0; j 5; j) {printf(%d , *(temp_arr));//temp_arr ptr[i]可简化}printf(\n);}return 0;
}9. 数组指针的应用 一定要复习前面的指针所指向的类型再来学以下内容。 例1
//数组指针
int main() {int arr[] {1, 2, 3};//arr arr[0] 是数组的首元素地址 思考arr是什么答数组地址数组指针//顾名思义数组指针就是存放数组地址的指针。printf(%p %p %p, arr, arr, arr[0]);//地址相同但含义不同int (*p)[3] arr;//(*p)代表这是一个指针[3]代表p指向的是一个长度为3的数组int代表该数组的元素是int类型的。printf(%p , *p);return 0;
}ps: 数组名是数组首元素地址这个公式有两个例外
1.sizeof数组名2. 数组名 这两种情况的数组名都是代表的整个数组
例2:
void print_arr(int (*parr)[3][5], int r, int l) {//*parr//解引用拿到的是首元素地址即第一行for (int i 0; i r; i) {int (*temp)[5] *parr i; //每一行for (int j 0; j l; j) {printf(%d , *(*temp j));}printf(\n);}
}int main() {int arr[3][5] {1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7};print_arr(arr, 3, 5);//arr代表是整个数组:传参给数组指针return 0;
}思考
int (*p)[5] // 数组指针指向的数组是int [5]int (*p[10])[5]是什么//数组指针的数组
即把原来那个数组指针复制十份。得到p是一个长度为10的数组该数组里面存放的是数组指针每一个数组指针指向的数组类型是int[5]。
10. 指针的传参
实参写什么
一级指针作为函数参数时它能接受的参数类型有只要是地址就可以。
二级指针作为函数参数时它能接受的参数类型有1.二级指针2.一级指针的地址 3.存放一级指针的指针数组
11. 函数指针
函数指针是什么函数指针就是存放函数地址的指针。
讲到的用法时我们回顾一下数组名加的区别数组名就是首元素地址相当于数组名[0]而数组名是取出整个数组的地址它代表的是整个数组的地址。
不同于数组名而函数名 、函数名 是完全等价的。也就是说无论加不加他们都表示取出函数的地址。
例1
int Add(int a, int b) {return a b;
}
int main() {int (*pf)(int, int) Add; //加不加都是代表Add的地址int x (*pf)(1, 3);//4int y pf(1, 4);//5 //Add pfprintf(%d %d, x, y);return 0;
}函数指针的定义很类似于数组指针。比如上例取出函数Add的地址给到我们的指针pf因此(*pf)表示这是一个指针(也是为了防止与后面的括号结合)*pf(int, int)表示该指针指向的是函数最后前面的int 代表该函数的返回类型是int。
例2
(*( void(*)())0) ();将0强转为函数指针类型解引用拿到这个函数再调用函数。
例3
void(* signal(int,void(*)(int)) )(int);signal是一个函数指针这个函数指针内将函数指针作为参数即函数指针套函数指针。
12. 函数指针数组
void (*p[5]) (int,int)//*p[5]去掉就是它的类型可见它是一个函数不加括号会报错例1
//计算器模拟int cal_add(int x, int y) {return x y;
}int cal_sub(int x, int y) {return x - y;
}int cal_mul(int x, int y) {return x * y;
}int cal_div(int x, int y) {return x / y;
}int main() {int (*cal[5]) (int, int) {0, cal_add, cal_sub, cal_mul, cal_div};//将函数指针存放到函数指针数组中int op 999;while (op ! 0) {printf(请输入两个数\n);int x, y 0;scanf(%d, x);scanf(%d, y);printf(请选择操作符exit:0 :1 -:2 *:3 /:4 \n);scanf(%d, op);int res cal[op](x, y);//取出函数指针进行调用printf(%d\n, res);}return 0;
}指向函数指针数组的指针
int (*f_arr[5])(int,int) //函数指针数组
int (*(*p)[5])(int,int) f_arr;//要加表示整个数组,去掉*p就是它的类型去掉*p后表示它的类型为函数指针数组因此叫指向函数指针数组的指针。
13. 回调函数
回调函数就是把函数作为形参进行处理最后返回这个形参函数这样的函数就叫回调函数。
例1
int f1(int x) {x;return x;
}int f2(int (*pf)(int), int z) {//回调函数return pf(z);
}int main() {int y f2(f1, 2);printf(%d, y);return 0;
}在例1中pf作为函数指针充当形参最后再返回它自己。
首先由于f2(f1,2)是一个回调函数它把pf充当f1最后其实调用的就是f1(2)得到3
那么它的实际意义是什么呢我们再来看一个例子。
假如我们把之前学习函数指针数组用到的计算器程序进行改装一下
例2
int cal_random(int (*cal)(int,int),int x,int y){return cal(x,y);
}
int main(){cal_random(cal_div,3,4);//3/4cal_random(cal_mul,1,8);//1*8
}由此可见我们可以把通过回调函数来解决多个重复类型函数的定义所带来的代码冗余。此外这样的代码更加具备可读性。