5,数组指针的不同含义

int a[5][10];

printf(%d, %d, %d\n", a, a+1, &a+1);  //1310392,1310432,1310592

a和&a都是数组a[5][10]的首地址

a 是 int a[10]的类型,而 &a 则是 a[5][10]的类型。指针运算中的”1“代表的是指针类型的长度。所以 a + 1 和 &a + 1 中的1代表的长度分别为 a 的类型 a[10]即 sizeof(int)*10 和 &a 的类型 a[5][10]即 sizeof(int)* 10 * 5。所以 a首地址的输出为1310392,那么 a + 1 和 &a + 1的地址为:

a + 1 = 1310392 + sizeof(int) * 10 = 1310392 + 4 * 10 = 1310432

&a + 1 = 1310392 + sizeof(int) * 10 * 5 = 1310392 + 4 * 10 * 5 = 1310592

更抽象的说,

如果定义一个数组 int a[M1][M2][...][Mn], 那么 a + 1 = a 首地址 + M2 * M3*...*Mn * sizeof(int);

而 &a + 1 = a首地址 + M1*M2*...*Mn * sizeof(int)。

练习:分析下面程序计算结果

int i = 0, j = 20, *p1 = &i, *p2 = &j;

void f(int **ptr1, int *ptr2)

{

int *tmp = ptr2;

**ptr1 *= 10;

*ptr2 *= 10

ptr2 = *ptr1;

*ptr1 = tmp;

}

调用 f(&p1, p2)之后, i, j, p1, p2的值各是什么?

int *tmp = ptr2; 即 tmp = &j, tmp指针指向了变量j。

**ptr1 *= 10; **ptr1 即 *(*ptr1)即*p1即 i。**ptr1 *= 10 即为 i *= 10; 即为0;所以i 的值为0;

*ptr2 *= 10;   *p2 *= 10 即 j *= 10 即为200; 所以j 的值为200。

ptr2 = *ptr1;   则为p2 = *ptr1 即0为 p2 = p1;

*ptr1 = tmp;   则为 p1 = tmp = &j;

因此答案: i = 0; j = 200; p1 = &j; p2 = &j。

6,定义一个宏,求出给定结构中给定成员的偏移量。

#define OFFSET(TYPE, MEMBER) (size_t)(&(((TYPE*)0)->MEMBER))

(size_t)(&(((TYPE*)0)->MEMBER))把0址址转化为TYPE结构的指针,然后获取该结构中MEMBER成员的指针,并将其强制转换为size_t类型。由于结构从0地址开始定义, 因此,这样求出的member成员地址,实际上就是它在结构中的偏移量。在Linux 的内核中就是用这样的宏定义来求成员的偏移量的。(详:include/linux/stddef.h)。

7,分析程序运行结果:

#include <stdio.h>

int main(void)

{

int a[5] = {1, 2, 3, 4, 5};

int *ptr1 = (int*)(&a + 1);

int *ptr2 = (int*)((int)a+1);

printf(%x,%x", ptr[-1], *ptr2);

return 0;

}

&a 和 a 都表示数组的首地址,但是它们代表的类型不同。其中&a代表整个数组,而 a 代表数组的第一个元素,即&a+1 中的1代表的大小是整个数组,而a+1中1的大小代表的是一个元素的大小。

指针加减法运算,后面的数字表示指针指向的数据类型的大小的倍数。

比如 &a+1, 其中的 1 就表示指针向前移动 1*sizeof(&a)那么多的字节。而 &a 表示整个数组,所以 ptr1 = (int*)(&a + 1), ptr1指到了数组的末尾位置。因为 ptr1[-1]即为*((int*)ptr1 -1),即指针ptr1向低地址移动sizeof(int)个字节,即向后移动4个字节,正好指向到a[4]的位置,所以ptr1[-1]为5。

对于语句 *ptr2 = (int*)((int)a+1), 在这里,我们已经将指针a强制转换成了整型,a+1不是指针运算了。(int*)((int)a+1)指向了首地址的下一个字节。所以*ptr2所代表的整数(四个字节,且低位优先)是2000000。

8,指针与引用的区别

引用是一种没有指针语法的指针。与指针一样,引用提供对对象的间接访问。引用为所指对象的一个别名。

int i = 0;

int &refi = i; // refi指向一个i引用。

引用必须初始化,而指针没有这个要求(尽管没有初始化的指针很危险);引用总是指向它最初获得的那个对象,而指针可以被重新赋值。

C++中向函数中传递指针和传递指针的引用的区别是

如果是传递指针,那么会先复制该指针,在函数内部使用的是复制后的指针,这个指针与原来的指针指向相同的地址,如果在函数内部将复制后的指针指向了另外的新的对象,那么不会影响原有的指针。所以在函数中改变指针,必须传递指针的指针或者指针的引用。

使用对象指针作为函数参数比使用对象作为函数参数更普遍。因为使用对象指针作函数参数有如下好处。

(1)实现传址调用。可在被调用函数中改变调用函数的参数对象的值,实现函数之间的信息传递。

(2)使用对象指针实参仅将对象的地址传给形参,而不进行副本的拷贝,这样可以提高运行效率,减少时空开销。

使用对象引用作函数参数要比使用对象指针作函数更普遍,这是因为使用对象引用作函数参数具有用对象指针作函数参数的优点,而且更简单、更直接。

9,指针的引用修改指针

在C 语言中经常使用指针、指针的指针、指针的引用作函数的参数。那么它们的区别是什么呢?

(1)指针引用作参数

void func(MyClass *&pBuildingElement); //指针的引用能修改指针

(2)指针作参数

void func(MyClass *pBuildingElement); //指针,不能修改指针

(3)指针的指针作为参数

void func(MyClass **pBuildingElement); //指针的指针,能修改指针

下面是三个实际函数调用的例子:

void func1(MyClass *pMyClass)
{
     DoSomething(pMyClass);
     pMyClass = pOtherObject; //其他对象的指针
}

MyClass *p = NULL;
func1(p); //指针作参数, p不参被改变值

void func2(MyClass **pMyClass)
{
     *pMyClass = new MyClass;
      ....
}

MyClass *p = NULL;
func2(&p); //指针的指针作参数,p的值可以被改变

void func3(MyClass *&pMyClass)
{
      pMyClass = new MyClass;
}
MyClass *p = NULL;
func3(&p); //指针的引用作参数,p的值可以改变

下面指针引用与指针比较:这两个函数功能都是获取给定位置的元素。

Cobject *&GetAt(POSITION position);//返回对象指针的引用,可能修改函数返回的对象

Cobject *GetAt(POSITION position); //返回对象的指针, 不可以修改函数返回的对象。

所以如果写成下面这样:

Cobject * pObj = myList.GetAt(pos);则pObj 返回的是列表中某个对象的指针。如果接着改变pObj的值:

pObj = pSomeOtherObj; 这改变不了在位置 pos 处的对象地址,而仅仅是改变了变量pObj。但是, 如果写成下面这样:

Cobject *& rpObj = myList.GetAt(pos);

现在,rpObj是返回的引用列表中的对象的指针, 所以当改变rpObj时, 也会改变列表中位置pos处的对象地址,也就是说替代了列表中的这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的值,另一个则不能。

事实上,函数可以返回任何类型的引用,不仅仅是指针的引用。如:

int& abc(int &x); //函数中&的作用就是对变量的引用。

int x = 0;

int &a(int &i)//传入实参的引用

{

i = -1;

return x; //返回i的引用

}

void main(void)

{

int j = 10;

a(j) = 100;

//这个时候 j = -1, x = 100了, 因为函数a()返回了x的引用,可以修改x的值

}

总之, 返回引用就是返回一个变量的地址里面的内容,就是真正返回这个变量本身,它可以用作左值,以改变返回的引用的变量的值。在上面的代码中, 函数传入的是实参的引用,返回的是x的引用。因此在main()函数调用了a()函数之后, j 和 x的值都会发生改变。

返回一个类型的引用,在操作符重载赋值运算符”=“中, 这种方式是经常用到的。

指针注意的问题总结:
(1)指针在声明的时候最好初始化。

指针变量没有被初始化, 任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会随机的指向任何一个地址(即野指针),访问野指针会造成不可预知的后果,所以,指针变量在创建的同时应该被初始化,要么将指针设置为NULL, 要么让它指向合法的内存。

(2)指针的加减运算移动的是指针所指类型的大小

前面已经提到, 指针的加法运算p = p + n中, p向前移动的位置不是n个字节,而是n*sizeof(*p)个字节,指针的减法运算与此类似。

(3)当用malloc或new为指针分配内存时应该判断内存分配是否成功,并对新分配的内存进行初始化。

用malloc或new分配内存,应该判断内存是否分配成功。如果失败,会返回NULL,那么就是防止使用NULL指针了。在分配成功时,会返回内存的地址。这个时候内存是一段未被初始化的空间,里面存在的可能是垃圾数据。因此,需要用 memset等对该内存进行初始化。

此外,应该防止试图使用指针作为参数,支分配一块动态内存。如果非要这么做,那么请传递指针的指针或指针的引用。

(4)如果指针指向的是一块动态分配内存,那么指针在使用完后需要释放内存,做到谁分配谁释放,防止内存泄漏。

(5)指针在指向的动态内存释放后应该重新置为NULL, 防止野指针。

野指针不是NULL指针,是指向”垃圾“内存的指针,野指针是很危险的,它可能会造成不该访问的数据或不该改的数据被访问或者篡改。在应用free或者delete释放了指针指向的内存之后, 应该将指针重新初始化NULL。这样可以防止野指针。

void GetMemory(char**p, int num)

{

*p = (char*)malloc(num);

}

int main(void)

{

char *str = NULL;

GetMemory(&str, 100);

strcpy(str,"hello");

free(str);

if(str!=NULL)

{

strcpy(str,"world");

}

printf("\n str is %s\n", str);

getchar();

}

上代码中,它通过指针的指针分配了一段内存,然后将”hello“ 拷贝到该内存。使用完后再释放。到此为止,代码没有任何问题。但是在释放之后,程序又试图去使用str指针。那么这里就存在问题了。由于str没有被重新置为NULL, 它的值依然指向了该内存。因此后面的程序依然能够打印出”world”。

char *func()

{

char c = 'A';

char *p = &c;

return p;

}

void main(void)

{

char *pc = NULL;

pc = func();

printf("%c", *p);

}

在上面的代码中, func()函数试图返回一个指向局部变量c的指针。然而局部变量的生命期为func()函数的执行期,即变量c分配在栈上,func()函数执行完成后, c 就不存在了。返回的指针就是一个无效的野指针。因此,打印*p时,可能会出现任何一个不可确定的字符。

C/C++入门基础---指针(2)的更多相关文章

  1. C/C++入门基础----指针(1)

    指针其实就是一个变量, 和其他类型的变量一样.在32位计算机上, 指针占用四字节的变量.指针与其他变量的不同就在于它的值是一个内存地址,指向内存的另外一个地方, 指针能够直接访问内存和操作底层的数据, ...

  2. JavaScript入门基础

    JavaScript基本语法 1.运算符 运算符就是完成操作的一系列符号,它有七类: 赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=).算术运 ...

  3. C++ STL编程轻松入门基础

    C++ STL编程轻松入门基础 1 初识STL:解答一些疑问 1.1 一个最关心的问题:什么是STL 1.2 追根溯源:STL的历史 1.3 千丝万缕的联系 1.4 STL的不同实现版本 2 牛刀小试 ...

  4. C语言入门基础整理

    学习计算机技术,C语言可以说是必备的,他已经成为现在计算机行业人学习必备的,而且应用也是十分的广泛,今天就来看看拥有几年c语言工作经验的大神整理的C语言入门基础知识,没有学不会,只有不肯学. 结构化程 ...

  5. Java入门基础知识点总结(详细篇)

    Java入门基础知识点总结(详细篇)~~~~~目录 1.1 图解 1.1.1 Java基础知识点 1.1.2 Java基础语法的相关内容 1.2 关键字 1.3 标识符 1.3.1 标识符概念 1.3 ...

  6. mybatis入门基础(二)----原始dao的开发和mapper代理开发

    承接上一篇 mybatis入门基础(一) 看过上一篇的朋友,肯定可以看出,里面的MybatisService中存在大量的重复代码,看起来不是很清楚,但第一次那样写,是为了解mybatis的执行步骤,先 ...

  7. 01shell入门基础

    01shell入门基础 为什么学习和使用shell编程 shell是一种脚本语言,脚本语言是相对于编译语言而言的.脚本语言不需要编译,由解释器读取程序并且执行其中的语句,而编译语言需要编译成可执行代码 ...

  8. Markdown入门基础

    // Markdown入门基础 最近准备开始强迫自己写博文,以治疗严重的拖延症,再不治疗就“病入骨髓,司命之所属,无奈何”了啊.正所谓“工欲善其事,必先利其器”,于是乎在写博文前,博主特地研究了下博文 ...

  9. HTML入门基础教程相关知识

    HTML入门基础教程 html是什么,什么是html通俗解答: html是hypertext markup language的缩写,即超文本标记语言.html是用于创建可从一个平台移植到另一平台的超文 ...

随机推荐

  1. equal与==

    首先做的是比较引用,引用的如果是同一个对象,直接返回true.做完return就结束了.如果引用不是同一个地址,就往下走,判断是否是String的一个实例.同样,不是的话直接返回.是的话,拿字符串的长 ...

  2. Python 练习

    1.有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中.即: {'k1': ...

  3. Scorpio-CSharp简介

    Scorpio-CSharp是为了解决Unity游戏各个平台热更新的问题,纯c#实现 基于.net2.0 兼容所有c#平台 语法类似 javascript, 设计初衷是为了做一个所有人都能修改的热更新 ...

  4. EI目录下载地址及保护密码

    EI目录下载地址:http://www.elsevier.com/solutions/engineering-village/content EI工作薄保护密码:AAAAABABAABD

  5. 转 LoadRunner 技巧之协议分析

    在做性能测试的时候,协议分析是困扰初学者的难题,选择错误的协议会导致Virtual User Generator 录制不到脚本:或录制的脚本不完整,有些应用可能需要选择多个协议才能完整的记录 客户端与 ...

  6. 控制input框不能更改里面的内容

    <input type="text" disabled="true"/> 这个是给input设置一个属性.控制它可以不能改变里面的内容.已经试过了! ...

  7. css之页面两列布局

    两列布局:左边固定,后边自适应 第一种方法:左边的div左浮动或者是绝对定位,右边的div加margin-left:左边div的宽度 html部分 <div class="left&q ...

  8. UNIX网络编程——getsockname和getpeername函数

    UNIX网络编程--getsockname和getpeername函数   来源:网络转载   http://www.educity.cn/linux/1241293.html     这两个函数或者 ...

  9. Content has been consumed

    if(response.getEntity() != null && response.getEntity().getContent() != null) { message = IO ...

  10. python的rename原来这么用

    本来想实践应用一下如何批量修改,后来一想怎么那么麻烦,连最基本都都不会,简化到这份上再慢慢复杂之 一开始用help(os.rename)查了该方法的用法, 出来的解释太简单了,以为路径用的是和wind ...