指针是c/c++ 精华,没有很好的掌握指针,基本是没有掌握c/c++,对c/c++ 也是一知半解,往往指针掌握不好,也不能很好的理解数组和内存管理
一、指针在系统同占用的空间
在32位系统中,指针所占空间的大小为 4个字节,与指针指向的数据类型无关;
比如 int *p; char *p; double *p; int **p; 已经执行构造类型的结构体的(比如指向 结构体,联合体等的指针)大小均为4个字节
int *p; 表示内存中分配四个字节的内存空间,并将这块空间命名为p,并且这块内存p 中存入的数据都当做地址来看,大小是4个字节
二、int *p = NULL 和*p = NULL 区别
(1) int *p = NULL;
表示定义了一个指向int 类型的指针 p(p指向的内存用来存放int类型的数据),并将p 初始化为NULL,即p 指向内存为NULL地方(内存地址0x00000000);
(2) int *p; *p = NULL;
表示定义了一个指向int类型的指针 p(p指向的内存用来存放int类型的数据),注意此处p 未被初始化,p可能指向内存中一个非法的内存地址,而这时对*p = NULL; 赋值
可能是对非法的内存地址进行赋值;
解析:区别:(1)表示对p赋值(定义p指向的内存地址)(2) p 指向未定义,p指向随机的一块内存地址,是对p指向的内存进行赋值
注意:定义变量的时候,定义的同时一定要对变量进行初始化;
三、将数值存储到指定的内存地址
现对内存为0x0012ff60 的内存进行赋值0x100(首先保证0x0012ff60这个内存地址是可以访问的)
(1) int *p = (int *)0x0012ff60;
*p = 0x100;
(2) *((int *)0x0012ff60) = 0x100;
四、数组内存布局
int a[10] = {0};
表示在内存中申请了一块内存 大小是 sizeof(int)*10,这块内存存放10个int 类型的数据,整块内存命名为a,这块内存没有名字,要访问内存中的各个元素只能通过数组
名a 加上下标或者偏移量的形式访问;
(1) a 作为右值的时候表示数组首元素首地址; sizeof(a) = 40 (32位机下)
(2) &a 表示数组的首地址, &a + 1 将偏移 sizeof(int) * 10 个大小的空间
(3)a 不能作为左值:a 的值不可改变(数组的访问要通过a加地址偏移或者下标形式访问)a 不能作为左值
比如a++ 是错误的, a++ 等价于 a = a + 1;(编译错误)
int *p = NULL; p = a + 1; (编译正确)
(4)&a[0]和&a 区别
虽然&a[0]和&a 值是相同的,但表示的意义不同:
&a[0] 表示a[0]的地址(数组首元素首地址)
&a 表示的是整个数组的首地址
五、指针和数组的关系
指针和数组无任何关系
(1)指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方都能通过这个指针变量访问到
(2)数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数
六、指针、数组访问
看下面一道例题:
- int main()
- {
- int a[5] = {1,2,3,4,5};
- int *ptr = (int *)(&a+1);
- printf("%d, %d", *(a+1), *(ptr-1));
- return 0;
- }
解析:
(1) &a + 1 表示自数组的首地址开始,偏移了&a + 5*sizeof(int) 大小的地址, 指向数组a[4]即 元素5 后面的元素;
(int *) (&a + 1) 然后强制类型转化为指向int 类型的指针,然后赋值给 ptr,ptr的偏移量n, 就转化为偏移 n*sizeof(int) 大小
所以,*(ptr - 1) 值为 5
(2) *(a+1) 表示数组首元素首地址偏移一个 sizeof(int) 的地址, 所以*(a+1) 值为2
七、代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组
八、指针数组和数组指针
(1)指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。 比如: int *p1[10];
(2)数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称
比如: int (*p2)[10];
九、指针数组经典例题
在x86 系统下,其值为多少?
- int main()
- {
- int a[4]={1,2,3,4};
- int *ptr1=(int *)(&a+1);
- int *ptr2=(int *)((int)a+1);
- printf("%x,%x",ptr1[-1],*ptr2);
- return 0;
- }
解析:
(1) ptr1 的值为 4 (解析见前面)
(2) 首先要确定系统是大端还是小端模式
确定大端小端模式函数: 函数返回值为1:小端模式;函数返回值为0:大端模式
- int checkSystem()
- {
- union test
- {
- int i;
- char ch;
- }a;
- a.i = 1;
- return ( a.ch == 1);
- }
若checkSystem() 返回值 为1,表示系统为小端模式, *ptr2 值为 2000000
若checkSystem()返回值 为0,表示系统为大端模式, *ptr2 值为 100
十、二维数组
1. 二维数组的地址
char a[3][4];
二维数组在内存中是以线性的形式存储的,char a[i][j] ;编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个数组
a[i][j]的首地址为&a[i]+j*sizof(char)。再把&a[i]的值用a 表示,得到a[i][j]元素的首地址为:a+ i*sizof(char)*4+ j*sizof(char)。同样,可以换算成以指针的形式表示:*(a+i)+j
2. 二维数组的初始化
- #include <stdio.h>
- int main(int argc,char * argv[])
- {
- int a [3][2]={(0,1),(2,3),(4,5)};
- int *p;
- p=a [0];
- printf("%d",p[0]);
- }
解析: 结果是 1,而不是0 注意例题中初始化的时候,用到的是括号表达式而不是大括号
在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号了。
十一、二级指针
char **p;
定义了一个二级指针变量p。p 是一个指针变量,在32 位系统下占4 个byte。它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。
注: 多维数组和多级指针可以根据二维数组和二级指针依次类推
十二、数组参数和指针参数
1. 不能向函数传递一个数组
比如:
- #include "stdio.h"
- void fun(char a[10])
- {
- ......
- }
- int main()
- {
- char b[10] = "abcdefg";
- fun(b[10]);
- return 0;
- }
解析:
(1):b[10] 代表的是数组的一个元素,不能代表数组
(2):b[10] 数组越界了
(3):void fun(char a[10]); 实际是要传递的是一个char类型的指针;参数可以改写为
void fun(char a[]); 或者 void fun(char *a);
(4):函数调用的时候可以改写成 fun(b);
总结:C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。
2. 不能把指针本身传递给函数
例一:
- #include "stdio.h"
- void fun(char *p)
- {
- char c = *(p+3);
- printf("%c\n", c);
- }
- int main()
- {
- char *p2 = "abcdefg";
- fun(p2);
- return 0;
- }
解析:
(1). p2 是main 函数内的一个局部变量,它只在main 函数内部有效。(注意:main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局
变量 一样长而已。全局变量一定是定义在函数外部的)
(2). 对实参p2做一份拷贝并传递给被调用的函数。即对p2 做一份拷贝,假设其拷贝名为_p2。那传递到函数内部的就是_p2 而并非p2 本身。
例二:
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- void GetMemory(char * p, int num)
- {
- p = (char *)malloc(num * sizeof(char));
- }
- int main()
- {
- char *str = NULL;
- GetMemory(str, 10);
- strcpy(str, "hello");
- free(str);//<span style="color:#000099;">free 并没有起作用,内存泄漏</span>
- return 0;
- }
解析:
通过编译可以看到,str的值仍未NULL。因为str 传到函数内部的是str的拷贝假设名字是_str, 当函数GetMemory(char *p, int num) 退出时,申请的内容
同时释放掉并未传递给str(malloc 的内存的地址并没有赋给str,而是赋给了_str。而这个_str 是编译器自动分配和回收的),导致内存访问异常;
例二:解决方法
(1) 增加return,将申请的内存返回给str,同时要用str 进行接收
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- char * GetMemory(char * p, int num)
- {
- p = (char *)malloc(num * sizeof(char));
- return p;
- }
- int main()
- {
- char *str = NULL;
- str = GetMemory(str, 10);
- strcpy(str, "hello");
- free(str);
- return 0;
- }
(2)使用二级指针
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- void GetMemory(char ** p, int num)
- {
- *p = (char *)malloc(num * sizeof(char));
- }
- int main()
- {
- char *str = NULL;
- GetMemory(&str, 10);
- strcpy(str, "hello");
- puts(str);
- free(str);
- return 0;
- }
解析:
这种方法真正的将str传到函数GetMemory(char **p, int num) 中
(1)GetMemory(&str, 10); 这里传递的是 str的地址,函数内部申请空间赋值给 *p(也就是str),malloc 分配的内存地址是真正赋值给了str
3. 二维数组参数和二维指针参数
void fun(char a[3][4]);
可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4 个char 类型数据的数组。上面的规则,“C 语言中,当一维数组作为函数参数的时候,编译器总是把它解
析成一个指向其首元素首地址的指针。所以上面表达式可以改为: void fun(char a[ ][4]); 或者 void fun(char (*a)[4]);
C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如
此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写
十三、函数指针
函数指针的形式之一: char * (*fun1)(char * p1,char * p2); (注意:此处的fun1 是指向函数的指针,而不是函数名,此函数是匿名的)
1、函数指针的使用
比如:
- #include <stdio.h>
- #include <string.h>
- char * fun(char * p1, char * p2)
- {
- int i = 0;
- i = strcmp(p1, p2);
- if (0 == i)
- {
- return p1;
- }
- else
- {
- return p2;
- }
- }
- int main()
- {
- char *str = NULL;
- char * (*pf)(char * p1, char * p2);
- pf = &fun;
- str = (*pf)("aa", "bb");
- puts(str);
- return 0;
- }
解析:
使用指针的时候,需要通过钥匙(*)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。这里需要注意到
是,在Visual C++6.0 里,给函数指针赋值时,可以用&fun 或直接用函数名fun。因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。
2. (*(void(*) ())0)() 的含义
解析:
(1)void(*)() 定义了一个函数指针
(2)(void(*)()0) 将0强制转化为一个函数指针,0是个地址,这个匿名函数保存于起始地址为0的一块内存中
(3)(*(void(*) ())0),这是取0 地址开始的一段内存里面的内容,其内容就是保存在首地址为0 的一段区域内的函数
(4)(*(void(*) ())0)(),这是函数调用
3. 函数指针数组
char * (*pf[3])(char * p);
解析:
pf 是数组名,这个数组是指针数组,数组中的每个元素均为指针(指向函数的指针)
例题:指针数组的使用
- #include <stdio.h>
- #include <string.h>
- char * fun1(char * p)
- {
- printf("%s\n",p);
- return p;
- }
- char * fun2(char * p)
- {
- printf("%s\n",p);
- return p;
- }
- int main()
- {
- char * (*pf[3])(char * p);
- pf[0] = fun1; // 可以直接用函数名
- pf[1] = &fun2; // 可以用函数名加上取地址符
- pf[0]("fun1");
- pf[1]("fun2");
- return 0;
- }