C语言:指针详解
今日有同学跟我反映,在学习指针的时候,有点懵懵哒,对于初学者来说,这才是应有的赶脚.
好了,废话就不过多的叙述了,我来给大家分析下指针到底是怎么一回事,有说不对的地方,欢迎大家指正,有些是引用,有些是原创,只要能让你明白一点点,也算我功夫没白费
在说之前先贴一段代码感受一下
#include <stdio.h> int main(int argc, const char * argv[]) {
int b = 10; /* 指针变量一般是不可以直接赋值的,因为直接赋值的话,就是让这个指针指向这个赋值的地址,万一这个地址是你计算机内部的某个程序的或者是系统的某个地址,而你对这个指针的值又改变了,那么你的计算机系统就会出现问题,可能导致系统不能正常运行活着程序出错等等。但是NULL是一个空地址,即0,它不指向任何地址,所以可以赋值为NULL */ int* p1 = NULL; printf("p1的地址为 == %p\n",p1);//0x0,此时不能访问它的值,会崩溃:例如(printf("p1的值为 == %d\n",*p1); int* p = &b; p = &b; *p = b; *p = 20; printf("&b的地址为 == %p\n",&b);//0x7fff5fbff7ec printf("&p的地址为 == %p\n",&p);//0x7fff5fbff7e0 printf("&*p的地址为 == %p\n",&*p);//0x7fff5fbff7ec printf("*p的地址为 == %d\n",*p);//20 printf("*p的地址为 == %d\n",b);//20 /* 综上所述 我是不是可以这么认为 &*p = &b = 同样的内存地址 那么 *p = b = 10,而 &p != &*p = &b ,所以&p取的是指针在内存中的地址 由此得出 *p = b, p为指针的变量名,*p则表示指针所指向的内存中存储的值 */
int a[3] = {1,2,3}; int *p = a; int **p1 = &p; //1点. printf("p的地址为 == %p\n",p);//0x7fff5fbff7ec printf("*p的值为 == %d\n",*p);//1 printf("*p + 1的值为 == %d\n",*p + 5);//6=1+5 printf("----------------------------------\n"); //2点 printf("&(*(p + 1))的值为 == %p\n",&(*(p + 1)));//0x7fff5fbff7f0 printf("*(p + 1)的值为 == %d\n",*(p + 1));//2 printf("&a[1]的地址为 == %p\n",&a[1]);//0x7fff5fbff7f0 printf("a[1]的值为 == %d\n",a[1]);//2 //综上可得出 &(*(p + 1)) = &a[1] *(p + 1) = a[1],但是如果这么操作:printf("(p + 1)的值为 == %d\n",(p + 1));这是不对的,报警告,类型不匹配 printf("\n"); printf("p1的地址为 == %p\n",p1);//0x7fff5fbff7d0 printf("*p1的地址为 == %p\n",*p1);//0x7fff5fbff7ec printf("**p1的值为 == %d\n",**p1);//1 printf("\n"); printf("&(*p)的地址为 == %p\n",&(*p));//0x7fff5fbff7ec printf("&(**p1)的地址为 == %p\n",&(**p1));//0x7fff5fbff7ec printf("&(*p1)的地址为 == %p\n",&(*p1));//0x7fff5fbff7d0 printf("\n"); return 0; }
说到指针,先说说地址,看一段小程序
#include "stdio.h" int main() { int a = 10; int *p = &a; printf("%p\n", p); //0x7fff8b6a378c return 0; }
那什么是地址呢?当然我帮你百科一下。是系统 RAM 中的特定位置,通常以十六进制的数字表示,系统通过这个地址,就可以找到相应的内容。当使用80386时,我们必须区分以下三种不同的地址:逻辑地址、线性地址、物理地址;在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干,比如上面那个"0x7fff8b6a378c" 就是逻辑地址。逻辑地址不是被直接送到内存总线,而是被送到内存管理单元(MMU)。MMU由一个或一组芯片组成,其功能是把逻辑地址映射为物理地址,即进行地址转换。下面是转换关系图。

指针
//字符串翻转例子 #include "stdio.h" #include "string.h" void revstr(char *); int main() { char str[] = "Zhen Shan Ren is good!"; revstr(str); puts(str); }
//翻转函数的实现 void revstr(char *str) { char *start, *end, temp; start = str; end = start + strlen(str) -1; while (start++ < end--) { temp = *start; *start = *end; *end = temp; } }
上面的例子是从指针的角度去处理字符串,我再revstr 函数中定义了两个指针,一个指针指向字符串的首地址,另一个指针指向字符串的末地址,把内容互换。 指针提供这样便利,可以通过加、减来访问这一块内存。然后再去改变内存的值。如果没有指针,只能去操作这样逻辑地址 “0x7fff8b6a378c”去计算下一个或上一个逻辑地址,会不会疯掉呢?所以指针把我们带入到地址层面去操作数据。指针难点是我们不是很清楚有些复杂的数据类型的在内存中存储。指来指去不知道指向那了。如果你能很清楚内存的分布,就不会指错地方!
指针的几个概念
1.指针的类型
基本数据类型比如 int、char ,还有 一些复杂的比如 int (*p)[], 指向数组的指针,像这种的判断就是指针名字去掉 , 指针的类型类型就是 int(*)[],其实就是指向数组的指针
2.指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。 你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:int*ptr:指针所指向的类型是int int(*ptr)[3]:指针所指向的的类型是int()[3]
3.指针的值
我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
看一段代码:这段代码是问你p1 是否和p2 相等?
#include "stdio.h" int main() { char *p1,*p2,*p3; char ch[] = {'a', 'b', 'c'}; char **pp; p1 = ch; pp = &ch; p2 = *pp; if (p1 == p2) { printf("p1 == p2\n"); } else { printf("p1 != p2\n"); } printf("p3 = %p", p3); return 0; }
//p1 != p2 //p3 = 0x4005f0dxy
&ch 指针类型为 char (*)[3], 当运行到pp=&ch 时候,编译器会骂你 “warning: assignment from incompatible pointer type” 指针类型不匹配(在vc6下直接报错)。看一下p3 会有一个值,未初始化指针是有内存地址的,而且是一个垃圾地址。不知道这个内存地址指向的值是什么。这就是为什么不要对未初始化指针取值的原因。最好的情况是你取到的是垃圾地址接下来你需要对程序进行调试,最坏的情况则会导致程序崩溃。以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
还有一个题目可以试试
#include "stdio.h"
int main()
{
int a[5] = {1,2,3,4,5};
int *p = (int *)(&a+1);
printf("%d,%d", *(a+1), *(p-1));
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️答案在此⬇️⬇️⬇️⬇️⬇️⬇️
指针与数组
“数组名就是指针”,“你就把当做指针理解”这是老师教的,却从不给个合理的解释,就像某组织教育无神论一样,你要信神就是迷信,我说这就是邪恶,缺乏对人最起码的尊重,当然在某组织的眼里我们都是奴才。好吧,假设数组名是指针
#include "stdio.h"
int main()
{
int a[] = {1,2,3,5};
int *p = a;
printf("a = %d, p =%d", sizeof(a), sizeof(p));
}
//output
//a= 16,p=4
从输出结果看两者根本就是两个事物,只能说数组名神似指针,数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;那么数组名到底是什么:
符号表是编译原理中的一个概念,应用于编译器的词法分析和语义分析两个阶段。词法分析的目标是让编译器能知道这是个数组就好了,那么语义分析阶段就需要确定这个数组的具体空间了。所以我们定义了一个数组,编译器就会在符号表中加入数组的名字a,并且根据其指定的大小,开辟一段内存空间,把这段内存空间的首地址(也就是第一个元素的地址)存入符号表,这也就是为什么我们通过数组名就可以去访问数组的元素了。编译器这么做是为了使我们使用数组更加的方便,易懂。也有人说a是一个内存地址,也没有什么不妥的,因为编译器允许我们直接把a作为数组首地址来用。数组是一种线性的数据结构,数组名指向了那一片内存。


浙公网安备 33010602011771号