▲ 更多精彩内容 请点击上方蓝字关注我们吧!
C语言中的指针是什么?
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。
用EEWrold论坛网友的话说,就非常好理解了。其实C语言操作内存的方式非常简单,CPU通过地址找到我们的内存(内存可以是内存条,显卡,USB等等设备….),内存的资源怎么样被找到?我们需要寻找到我们需要的资源,这就是我们经常在硬件中看到的寻址。通过寻找他的地址,也就是内存的门牌号,我们就可以找到这一片资源,然后才能去使用这里的数据。
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var_name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
指针的类型是如何定义的?
指针可以根据指针指向的变量的数据类型来进行分类,有整型指针,字符指针,数组指针,函数指针等等。
这两个是比较常见和容易理解的指针,依次用int*和char*表示,他们的区别在于指向变量类型不同,内存也不一样,在进行解引用操作时访问的字节大小也因为变量类型的区别会有所差异。整型指针可以访问4个字节,而字符指针只能访问1个字节。也就是说对整型指针变量解引用,一次可以操作一个整型,而对字符变量解引用一次只能操作一个字符。
较为特殊的char*p="hello"这并不是将整个字符串的地址传个了p,而是传了字符穿首元素‘h'的地址,可以通过’h‘的地址来找到整个字符串。此时出现char*p2=“hello”,p2和p代表的是同一处地址,因为hello是常量字符串,没有必要开辟两块不同的空间的来存储它。这是字符指针的一个特性。
void型的指针可以接受任何类型的地址,但是不能对void型指针进行解引用操作。解引用操作要有特定的访问字节的数量,比如对整型指针解引用就是访问4个字节,字符型指针解引用就是访问1个字节,而void型指针无法确定访问字节个数,所以不能进行解引用操作。同时void*这种类型的指针也不能进行加减整数的操作,因为无法确定跳过的字节个数。
这是一种指向数组的指针,例如int(*p)[10]这就是一个指向数组的指针,它指向的数组有10个元素,每个元素都是整型。给*p加上括号是因为p和[10]优先结合,这样的话就变成了一个数组而不是指针了。这个数组叫指针数组 ,int*p[10]这样的写法意思是一个有10个元素的数组,每一个元素都是整型指针,这和数组指针是两个不同的东西。
指向数组的指针里面存放的便是数组的地址,而非数组某个元素的地址,所以在定义数组指针时要用 &+数组名,而不是简单使用 数组名。
函数指针顾名思义就是指向函数的指针,每个函数都有一个入口,这个入口的地址便是函数指针所指向的地址。函数地址的表示方法为 函数名或 &+函数名。例如一个函数叫Add,&Add和Add都是表示这个函数的地址没有什么差别。函数指针的写法是 函数的返回类型(*)(函数的参数),例如函数Add,其函数指针的写法就是int(*p)(int,int)=Add 。*p要加上括号来保证*和p的优先结合来形成一个指针变量,如果不加括号来优先结合,则会出现int* p(int,int)这样的写法,这就变成了函数的声明,这个函数的返回类型是int*,函数的名字叫p,函数的参数是2个整型和原先的函数指针不是同一个意思。
用函数指针调用函数时可以不加*这个解引用符号,因为这个符号将不会在程序运行的时候起到作用。
指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。 比如:char*的指针解引用就只能访问一个字节,而 int*的指针的解引用就能访问四个字节。
C语言指针变量
如何使用指针变量进行运算?
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
指针与数组之间关系
一般系统或编译器会分配连续地址的内存来存储数组里的元素,如果把数组地址赋值给指针变量,那么就可以通过指针变量来引用数组,读写数组里的元素了。我们来做个实验:
从这个代码来看,定义了一个数组buff并初始化为1,2,3,4,5。
定义了2个指针变量p1和p1,分别指向buff, &buff[0]。
buff默认的是数组下标为0元素的存储地址。
所以这里buff和&buff[0]是同一个内存地址,只是写法不一样。
我们从输出结果可以看的出来,数组和指针变量的地址都是一样的,所以大家用这几种写法都是可以的。
那么我们来看下输出结果,都是1,说明操作是对的。
指针进行运算时要注意以下几点:
举例说明:
1 #include
2 int main(){
3 int a = 10, *pa = &a, *paa = &a;
4 double b = 99.9, *pb = &b;
5 char c = '@', *pc = &c;
6 //最初的值
7 printf("&a=%#X, pa=%#X, pb=%#X, pc=%#X\n", &a, pa, pb, pc);
8 //加法运算
9 pa++; pb++; pc++;
10 printf("&a=%#X, pa=%#X, pb=%#X, pc=%#X\n", &a, pa, pb, pc);
11 //减法运算
12 pa -= 2; pb -= 2; pc -= 2;
13 printf("&a=%#X, pa=%#X, pb=%#X, pc=%#X\n", &a, pa, pb, pc);
14 //比较运算
15 if(pa == paa){
16 printf("%d\n", *paa);
17 }else{
18 printf("%d\n", *pa);
19 }
20 return 0;
21 }
运行结果:
&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
2686784
从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?
以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:
刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。
如果pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:
这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。
如果pa++;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,如下图所示:
我们知道,数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加 1 就表示指向下一个元素,减 1 就表示指向上一个元素,这样指针的加减运算就具有了现实的意义,不过C语言并没有规定变量的存储方式,如果连续定义多个变量,它们有可能是挨着的,也有可能是分散的,这取决于变量的类型、编译器的实现以及具体的编译模式,所以对于指向普通变量的指针,我们往往不进行加减运算,虽然编译器并不会报错,但这样做没有意义,因为不知道它后面指向的是什么数据。
最后说说指针的复杂形式
双重指针是指向指针的指针,它是一个指针,这个指针指向某个内存地址,该地址的值是一个指针,指向给另一个内存地址(通常异于前者,但不排除二者相等)。
本质上,指针值就是内存地址。但为了防范指针值被滥用(如内存访问时越界),可以规定指针类型为强类型,即指针值及保存在该内存地址的对象的类型。双重指针不过是这种强类型的一个应用:该地址空间长度为一个指针长度(4或8字节),对象类型为另一种指针。
指针数组:就是一个数组,数组的各个元素都是指针值。
数组名出现在表达式中时,绝大多数情况(除了数组名作为sizeof的操作数或者作为取地址&元素符的操作数)会被隐式转换为指向数组的首个元素的指针右值。
当数组名作为取地址&运算符的操作数,则表达式的值为指向整个数组的指针右值。
例子:
chars[]="hello";
intmain(){
char(*p1)[6]=&s;//OK!
char(*p2)[6]=s;//compile error: cannot convert 'char*' to 'char (*)[6]'
char*p3=&s;//compile error: cannot convert 'char (*)[6]' to 'char*'
}
根据上述C语言标准中的规定,表达式 &s 的值的类型是char (*)[6],即指向整个数组的指针;而表达式 s 则被隐式转换为指向数组首元素的指针值,即 char* 类型。同理,表达式 s[4] ,等效于表达式 *(s+4)。
主条目:函数指针
指向函数的指针:不同于指向数据类型的指针,函数指针指向一段可执行的代码的首地址,这段代码仍然占用了一块内存空间。很多人都说C语言是一种面向过程的语言,因为它最多只有结构体的定义,而没有类的概念。根据本段所述,可以认为C语言能成为面向对象的语言,只是表述比较麻烦而已。事实上很多开源程序都使用这种方式组织他们的代码。
#include
voidinc(int*val)
{
(*val)++;
}
intmain(void)
{
void(*fun)(int*);
inta=3;
fun=inc;
(*fun)(&a);
printf("%d",a);
return0;
}
本文将C语言指针进行了系统性总结,实际开发中,还要是活学活用。未来,EEWorld还将针对工程师难题,推出更多硬核内容。
参考文献
· END ·