C 语言指针的基本知识

来源:互联网 发布:淘宝怎样积累粉丝 编辑:程序博客网 时间:2024/06/11 12:50

指针的描述

指针就是一个指向一串连续空间首地址的变量,这个变量叫做指针变量,而这串空间可能是整形,字符型,浮点型等等类型的空间

特殊的void *p:可一指向任何类型的变量空间,但void指针不能进行指针运算,即不能使用p++

在同一个系统下不管指针变量指向什么类型的变量空间,指针的大小总是不变的(其地址编号长度不变),在32位系统下指针变量存放4位无符号整数的十六进制编号表示指向内容的地址(4个字节的无符号整数),64位系统下用8位无符号整数的十六进制编号表示(8个字节的无符号整数),这个编号就是地址编号

指针与内存

在内存中一块地址空间的最小单位为1BYTE

对于内存,每个BYTE都有一个唯一的地址编号,这个编号也叫内存地址
一个地址编号对应的就是一个BYTE的空间大小

地址编号1 ——>BYTE
地址编号2 ——>BYTE
地址编号3 ——>BYTE
地址编号4 ——>BYTE
一个int占用4个BYTE,所以一个int就是占用了4个地址编号

由于地址编号在32位系统下用4位无符号整数表示,固然最多能表示2的32次方(0xffffffff)个内存地址,所以32位系统就只能使用4G内存,而64位系统则可以使用更多,当然这也要看cpu的总线个数,不过32位cpu应该装不上64位的操作系统吧

指针常量与指向常量的指针

指针中两个容易混淆的定义:

const int *p//指向常量的指针

这里p是一个变量,指向一个”常量”,不能通过p来修改指向能容的值

注意:这里的常量加了引号,这里可能是个”假的”常量

这里在C语言中,可以指向常量或变量,而且在c语言中即使定义一个常量,还是可以使用一个非指向常量的指针来修改常量的值,但在C++中不行,所以在C语言中定义常量更多的是使用define,define是进行文本替换,所以不会改变

int *const p //指针常量

p是一个常量,指向一个常量或变量,只能在定义时,初始化指向某个地址,以后不能修改指向,但可以修改指向内容的值

同刚才讨论的一样,这里比较特殊的情况是:在c语言中使用 int * const p 如果指向一个const定义的常量,则可以通过p修改常量的值

指针与数组

普通的指针与普通的数组

char a[100]="hello word";//定义了一个数组,有100个char,并初始化位数组成员变量char *s="hello word";//定义了一个字符串常量,s指向这个常量的首地址a[0]='a';//合法的,数组a的所有成员都是变量s[0]='a';//不合法,s指向的位一个字符串常量

所以我们这里使用const char *s=”hello word”;

同时我们也应该知道,数组名不可以做左值,数组名就是一个常量,而指针可以做左值,指针只是在定义时指向了一个地址,是可以进行运算的

指向数组的指针

在c语言中指针与数组的关系是非常紧密的,当一个指针指向数组时,指针名可以当数组名使用,固指针可以使用p[0],p[1]…

char a[100]="hello word";//定义了一个数组,有100个char,并初始化位数组成员变量char *s=a;//定义了一个指向数组的指针s[0]='a';//合法的,s[0]就是a[0]

指针数组

这里我们可以像使用数组一样使用指针,当然这里不只存在普通的数组,这里还可能存在一个存放指针的数组,我们称之为指针数组

int *a[];//定义了一个指针数组int **p;//定义了一个二级指针p=a;//让二级指针指向指针数组

这里又多出了一个新的名词,二级指针,其实二级指针就是指向指针的指针,它指向的空间存放着一个指针,这里是把这个二级指针指向指针数组的首个指针,即存放着指针数组首个成员的地址,而特殊的是这个成员也是一个指针,这里在使用时我们可以把它当成一个二维数组来使用

这里由于定义的为int类型,取值时一般为 p[1,2,3…][0],因为这里一个地址就对应一个整数,如果是char类型的指针数组的话,这个指针姑且有可能指向一个字符串,可以使用p[1]p[1,2,3,4..]

还有就是这里的指针数组里,数组元素是指针,所以这里的指针的地址是连续的,但其所指向的数据的地址并不相同,所以要仔细区分一下

多级指针的理解

int a=10;int *p1=&a;int **p2=p;

这里我们可以把’*’理解为取值符号,我们知道p1本身是一个二级指针,存放着一级指针p1的地址,我们使用*p1取值后把一级指针的值取了出来,那一级指针里存放的是什么呢,显然是变量a的地址,所以*p存放的为a的地址,这里我们如果执行如下操作

int b=20;*p2=&b;

执行过后这里p1就会指向b,而后无论使用 **p2还是*p1取值都会是20

同理再多的级指针原理都是一样的

int a=10;int *p1=&a;int **p2=&p1;int ***p3=&p2;int ****p4=&p3;...../*p4 是一个 int **** 类型的指针,存放的为 int *** 类型的地址(指向 int *** 类型)*p4 是一个 int *** 类型的指针,存放的为 int ** 类型的地址(指向 int ** 类型)**p4 是一个 int ** 类型的指针,存放的为 int * 类型的地址(指向 int * 类型)***p4 是一个 int * 类型的指针,存放的为 int 类型的地址(指向 int类型)****p4 是一个 int 类型,存放的为变量的值*/

指针与函数

c语言中想通过函数内部修改实参的值就必须通过传递实参的地址来间接修改实参的值
所以我们在用scanf进行复制时就必须传递实参的地址,使用&符号

当一个数组名做为函数形参时,其实就是一个指针变量名,也就是说如下这些写法都一样

void test(int a[10]);void test(int a[]);void test(int *a);

我们一般使用第三种

我们有时候为了不让函数修改指针内容的值,就会使用const int *a,不让在函数内使用*a修改,但这是在c语言中,我们说过了,c语言的const永远是一个假的const,我们仍可以使用如下代码进行修改

void test(const int *a){    int *p=(int *)a;    p[2]=100;}

我们不可以用*a来改,就定义一个*p来改

注意:
如果一个数组做为函数参数,在函数中数组的长度是不可见的,应该再传递一个量,表示数组成员的数量,但如果这个函数的参数是字符串(字符数组),则不需要传递数组的成员数量,字符数组总是以0结尾的,可以判断字符长度

指针的运算

我们知道,在数组中每个元素的地址都是不一样的,当然可能每个元素都存在不同地址中,虽然这些地址是连续的,单当同一类型的指针变量指向同一类型的数组的非首元素时,指针的使用就变得不那么友好

例如这里有一个int a[7];我们让一个整形指针指向&a[1]或使用了p++,当我们在使用*(p+3)=100是就会为a[4]赋值

int a[7]={1,2,3,4,5,6,7};int *p=&a[1];*(p+3)=100;

既然说到同一类型那就有不同类型,不同类型又会复杂一些

我们假设这里有一个int a=0x12345678;十六进制表示
而我们这里有定义了一个char *p=&a;
那这里我们输出*p会是什么?

int a=0x12345678;char *p=&a;prinft("%x\n",*p);

首先我们应该先想明白不是12就是78,这里一个char占用1个BYTE,一个BYTE用十六进制表示就是两位,而一个int占用4个BYTE,十六进制表示是八位

想明白为什么是12或78后这里又会涉及另一个知识:大端对齐与小端对齐
大端对齐就是大端对齐小端,小端对齐大端
小端对齐就是大端对齐大端,小端对齐小端

这里的大小端在地址编号中表现为地址的大小,在二进制中表现为低位与高位,这里是将数转换为二进制,需要注意的是二进制的低位在右边,高位在左边

在我们日常使用的操作系统中一般使用的都是小端对齐,所以这里的p[0]对应低位的78

通过函数再谈指针运算

初始化函数

#include <string.h>void *memset(void *s,int c,size_t n);//初始化函数

函数中第一个参数是指定要制空内容的首地址,第二个参数写0,第三个参数是要制空内容的大小,单位为字节,注意这里单位为字节

如果我们这样使用对吗

int a[10]={1,2,3,4,5,6,7,8,9,10};memset(a,0,10)

这样写对吗,显然不对,这里应该写memset(a,0,sizeof(a)); int类型的a[10]一共占用了40个字节

拷贝函数

memcpy(void *dest,const void *src,size_t n);//拷贝函数

功能是两块内存之间数据拷贝

第一个参数是目标地址,第二个参数是源地址,第三个产数是拷贝的字节
简单的拷贝就不再说明,这里说一个稍微复杂一点的拷贝,如下

short a[10]={1,2,3,4,5,6,7,8,9,10};int b[10]={0};memcpy(b,a,sizeof(a));

这里如果输出b会输出什么
20001
40003
60005
80007
a0009
00000
00000
00000
00000
00000

我们通过一张图来理解
内存拷贝

这里是使用十六进制来进行输入,这样比较容易理解,我们知道short在内存中占用2个字节,int占用4个字节,这里将short的低位拷贝到int的低位就变就变成了0201,而一块内存大小为1BYTE所以用十六进制表示就是00020001了,这里的高位0被省略,就变成20001,如果这里使用十进制表示的话就跟令人费解了,所以这里使用十六进制表示

内存的移动

memmove(void *dest,const void *src,size_t n);//内存的移动

至于内存的移动,与拷贝一样,就不多说,这里有一点需要注意的是不要出现内存重叠,即将一个a[10] 从a[0]拷贝5位到a[3],这里就会出现a[3],a[4]被覆盖

指针运算时,不要在意指针指向的是什么类型的地址,在意的是指针本身是怎么类型的地址

通过以上的了解,我们发现其实在c语言中任何数据类型的数据都可以理解为一个char *类型的数组

0 0