您的位置:首页 > 编程语言 > C语言/C++

C++指针详解(一)

2016-06-21 17:14 288 查看

前言

初学者对于指针往往云里雾里,对照课本看了半天,知道课本在说什么,以为是懂了,可到了真要用的时候,还是不懂。遇见具体的程序,分析不出个所以然。

根据我的学习经验,造成这种现象的原因在于指针涉及到的概念很多,不同的概念之间极易混淆,还有指针的输出不够直观,按常理讲,我们在打印的时候,打印出的是什么,我们便以为这个东西是什么,然而对于指针来说,这种规律却不适用,眼睛看到的东西不是真的,自然不能得到有效的反馈。其三,指针玩的溜的人一般都是大神,各种运算符花式使用,对于一个初学者来说,没被指针绕进去,先被运算符绕进去了。

于是,我针对指针应用中常见的一些混淆来分析指针,希望能帮到各位。

操作:声明,初始化,赋值,取值,移动。

运算符:&p *p p *p++ **p ……

对象:指针,数组,字符串,首地址,内存,指针变量,内容。

1 指针解释

1.1 声明:

int *p;

这里面有两层含义,p的类型为int*, *p的类型为int,合起来理解表示为指针变量p指向存储单元为int的一片内存。p本身的值是这片内存的首地址。

1.2 初始化

在指针未初始化之前,它是一个野指针,不指向任何内存,指针的初始化方法有多种,但不管哪一种,都不过是给指针变量P赋值而已。

(1) 已经命名的内存

测试代码:

int higgens = 5;
int *p = &higgens; // 或者 int *p; p = &higgens;

cout << "value of higgens is" << higgens
<< "; address of higgens is" << &higgens<<endl;

cout << "value of *pt is" << *p
<< "; value of pt is" << p << endl;


这种赋值的方法是,本来就存在一片内存(变量 higgens所位于的内存, 或者可以理解为变量是编译时分配的有名称的内存),需要做的就只是取出变量higgens的地址赋给p而已。

测试结果:



注意:其中&p是存放指针变量p的地址。

(2) 未命名的内存

指针的真正用武之地在于,在运行阶段分配未命名的内存,在这种情况下,只能通过指针来访问内存。

测试代码:

int *p = new int;
*p = 5;

cout << "value of *pt is" << *p
<< "; value of pt is" << p << endl;
delete p;


测试结果:



p可以看作是一种操作,可以称为取(赋)首地址中内容的操作( 为间接访问运算符)。

使用指针来创建动态数组,静态数组自身具有局限性,它不能动态地指定数组的大小。

例如:

char a = 3;

int table[a];

这种做法是错误的。

正确的做法:

char a = 3;
int *pp = new int[a];


上述两种指针的初始化方式都有一个共同点,指针指向内存的大小是给定好的。

常见的的错误,未分配内存就赋值:

char *a = "abc";
char *p = (char *)malloc(4);  //错误做法  char *p = null;
*p = *a;
cout << *a <<endl;


对于一个内存单元来说,单元的地址(编号)即为指针,其中存放的数据是该单元的内容,存放该指针(单元的地址)的变量称为指针变量。

混淆1:在很多初学者甚至是教程中没有把指针与指针变量进行分开。再次重申一遍,指针是地址,是常量。“指针变量”是指取值为地址的变量。

混淆2:打印数组的指针变量打印的是首地址,而打印字符串的指针变量打印的是整片地址中的内容

测试代码:

int p[3] = {1, 2, 3};
int *pp = new int[3];
const char* pt = "abc";
cout << "p is "<< p << "     pp is " << pp << "   pt is "<< pt <<endl;


测试结果



这是因为字符的相对于普通数组的特殊性,如果给cout提供一个字符(不一定是首字符)的地址,则它将从该字符开始打印,直到遇到空字符为止。

混淆3:运算符优先级混淆

p+1与(p + 1),前者是先取出首地址中的内容再加1,后者是首地址往后移一个内存单元再取出内容。

测试代码:

int a[3] = {1,3,5};
cout << "a is "<< a << endl;
cout << "&a is "<< &a << endl;
cout << "*a is" << *a <<endl;
cout << "*a is" << *a+1 <<endl;
cout << "*a is" << *(a+1) <<endl;


测试结果:



注意第三个与第四个的区别,间接访问运算符的优先级要高于加法运算符。

p++,由于++和同优先级,结合方向自右而左,等价于*(p++)。

测试代码:

int *p, i, a[10];
p=a;
for(i=0; i<10; i++)
*p++=i;
p=a;
for(i=0; i<10; i++)
printf("a[%d]=%d\n", i, *p++);




混淆4:数组名和数组的第一个元素的地址

两者是等价的。

int a[5], *pa;

pa=a; //数组名表示数组的首地址,可以赋予指向数组的指针变量pa

也可写为:

pa=&a[0]; //数组第一个元素的地址也是整个数组的首地址,也可赋予pa当然也可采取初始化赋值的方法:

int a[5], *pa=a;

混淆5:两种访问方式的混淆

int a[10]; int *p;

p=&a[0]//和p=a是等价的

a[10]是一个数组,a是数组名,它是一个包含10个int类型的数组类型,不是一般的指针变量噢!(虽然标准文档规定在c++中从int[]到int*直接转换是可以的,在使用的时候似乎在函数的参数为指针的时候,我们将该数组名赋值没有任何异样),a代表数组的首地址,在数字层面和a[10]的地址一样。这样我们就可以使用指针变量以及a来操作这个数组了。

所以我们要注意以下问题:

(1)p[i]和a[i]都是代表该数组的第i+1个元素;

(2)p+i和a+i代表了第i+1个元素的地址,所以我们也可以使用 (p+I)和(a+I)来引用对象元素;

混淆6:地址运算

p+1不是对于指针数量上加一,而是表示从当前的位置跳过当前指针指向类型长度的空间,对于win32的int为4byte;

理解了这个东西才能立即在编译器中为什么不同类新的指针不能相互赋值。

ok,接下来讨论一些高端的用法,这些东西是造成初学者对指针迷惑不清的元凶之一。

混淆7:指针变量所占内存,指针指向内存的大小,指针指向内存单元的大小

指针的大小(指针变量所占内存)

分析:既然指针只是要存储另一个变量的地址,。注意,是存放一变量的地址,而不是存放一个变量本身,所以,不管指针指向什么类型的变量,它的大小总是固定的:只要能放得下一个地址就行!(这是一间只有烟盒大小的“房间”,因为它只需要入一张与着地址的纸条)。 存放一个地址需要几个字节?答案是和一个 int 类型的大小相同:4字节。

则: sizeof(pInt)、sizeof(pChar)、sizeof(pBool)、sizeof(pFloat)、sizeof(pDouble)的值全部为:4。

测试代码:

char t[3] = {1,2,3};
char* p = t;
cout << sizeof(t) <<endl;
cout << sizeof(p) << endl;
cout << sizeof(char) << endl;
cout << sizeof(*t) << endl;


测试结果:3(指针指向内存大小),4(指针变量所占内存),1(内存单元大小),1(内存单元大小)

当指针s为数组指针时,sizeof返回数组所占内存的大小,当指针s为普通指针sizeof(s)返回指针变量所占内存大小。

也许你已经发现了,如果不是数组,如何返回内存大小呢?

答:没有这个功能。如果想要实现这个方法可以采用vector或者用类来封装。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: