C++ 指针与引用 知识点 小结

【摘要】

指针可以指向变量、数组、字符串、函数、甚至结构体。即指针可以指向不同数据对象。指针问题 包括 常量指针、数组指针、函数指针、this指针、指针传值、指向指针的指针 等。主要知识点包括:1.指针与引用在概念上的三个区别及其在const、sizeof、自增运算上的差异;2.熟记经典swap函数的指针实现与引用实现,并能反映输出错误的两个函数的思想弊端;3.熟记GetMem函数的错误形式以及错误产生的原因;4.比较数组、指针与静态变量作为函数返回值的差异;5.str、*str以及&str三者的关系;6.指针继承复指向中虚函数、实函数及变量间的关系;7.写出const指针,指向const的指针,指向const的const指针;8.分析高维数组指针与高维数组在取址上的差别;9.区分悬浮指针与空指针;10.new和malloc的常见问题,本质区别,为什么产生new,为什么不提出malloc;11.this指针3个点,为什么会有this,this什么时候用,this怎么传给函数的;12.句柄和智能指针。

【正文】

考点:指针与引用的区别

  • 指针需要进行合法性检测而引用不需要;
  • 指针可以赋值重新指向新的对象(不是地址、不是类型),但是引用只能指向初始化被指定的对象不能改变;
  • 指向一个对象且指向的对象不改变时应使用引用,若存在不指向任何对象或指向的对象存在改变时应采用指针;
  • 指针可以指向空值,但是引用不可以指向空值。因此,程序员可能有指向空值的时候,即允许变量为空的时候,应该使用指针;
  • 引用的代码效率要高于指针(具体体现在于能否在初始化时候不赋值)。
【补充】
1)引用没有const,指针有const,这里需要切记,使用const声明的同时必须要初始化;
2)指针指向一块内存,它的内容是所指向存的地址,引用时某块内存的别名;
3)sizeof 引用 得到的是所指向的变量的大小;sizeof 指针得到的是指针本身的大小;
4)指针和引用的自增运算意义不一样。
【例】

int &ref; 
// 错误,引用不能为空且需要同时初始化,因此错误!
int *p = 5; 
// 错误,指针位置向实际内存空间,赋值之后不知道存放的地址,没有指向,因此错误!
int *ptra,*ptrb;
int ptra = *ptrb;
// 错误,指针位置向实际内存空间,赋值之后不知道存放的地址,因此错误!
const int num;
// 错误,const 常量赋值必须同时初始化,因此错误!
考点:经典的 swap(int &a,int &b)函数
指针方式:传入的是地址,接收的是指针,处理的是改变指针的指向关系;
引用方式:传入的是变量(即,swap(int a,int b)),接收的是引用(地址,即 swap(int &ref_a,int&ref_b)),处理的是改变引用的指向关系,也就是引用变量本身;

误区
  • 采用变地址方式交换最终会释放改变成果;
  • 函数若采用指针变量做介质会导致内存泄露;

考点:指针申请内存空间
函数源码

void GetMemA(char *p,int num)
{
	p = (char*)malloc(sizeof(char)*num);
}
void GetMemB(char **p,int num)
{
	*p = (char*)malloc(sizeof(char)*num);
}

【解析】

因为,函数不能传值只能传址,所以,在函数内采用指针申请内存空间(即,void GetMem(char *p,int num))是不会成功的。那么为实现空间申请我们应该采用指向指针的指针(即,void GetMem(char **p,int num))。

假设,在 main() 函数中,变量 p 地址为 1 指向地址为 2(可以想象成变量 p 值为 2),*p 地址为 2 指向地址为 3(可以想象成变量 *p 值为 3),**p 地址为 3 存放一 char 型变量。

在 void GetMemA(char *p,int num)中,暂时称函数变量为 GetMemA.p 吧,很明显 GetMemA.p 是一个地址,指向一个 char 型变量。函数申请栈区进入函数后,GetMemA.p 地址为 4 ,指针变量获取一块长度为 num 的 char 型内存空间。函数调用结束,弹栈销毁 变量GetMemA.p 及其申请的空间,那么,主函数中的变量 p 没有得到任何改变;

在 void GetMemB(char **p,int num)中,暂时称函数变量为 GetMemB.*p 吧,很明显 GetMemB.*p 是一个地址,指向一个 char 型变量。函数申请栈区进入函数后,GetMemB.*p 地址为 5 ,指针变量获取一块长度为 num 的 char 型内存空间。函数调用结束,弹栈销毁 变量 GetMemB.p 及其申请的空间(注意,这里是变量 GetMemB.p 而不是 变量 GetMemB.*p),那么,GetMemB.*p 自然而然的保留了申请得到的内存空间,主函数中的变量 p 没有得到任何改变,而 *p 获得了一段长度为 num 的 char 型内存空间;

当然,函数还可写成这样直接返回内存空间。

char* GetMemC(char *p,int num)
{
	p = (char*)malloc(sizeof(char)*num);
}
【整型数据的源码传递】
#include 
using namespace std;
void GetMemory2(int *z)
{
    *z=5;
};
int main()
{
    int v;
    GetMemory2(&v);
    cout << v << endl;
    return 0;
}
【注】
总之,改变的是函数中元素指向的变量就能有效改变变量的数值,如果改变的是函数中元素本身,不论元素是地址还是别的,都会在函数调用结束后被释放掉。

字符串的关键知识点
区分数组字符串和指针字符串

char *strA()
{
    char str[] = "hello world";
    return str;
}
解析:
这个str里存在的地址是函数strA栈里“hello world”的首地址。函数调用完成,栈帧恢复调用strA之前的状态,临时空间被重置,堆栈“回缩”,strA栈帧不再属于应该访问的范围。这段程序可以正确输出结果,但是这种访问方法违背了函数的栈帧机制。只要另外一个函数调用的话,你就会发现,这种方式的不合理及危险性。【未理解这句话。】
——后记
OS认为这段内存是可以被使用的,该段内存内容的改变或者其他操作会使得该段内存不再被 str 正确调用。
如果想获得正确的函数,改成下面这样就可以:

char *strA()
{
    char *str = "hello world";
    return str;
}
首先要搞清楚char *str 和 char str[] :
char str[] = "hello world";是分配一个局部数组。局部数组是局部变量,它所对应的是内存中的栈。局部变量的生命周期结束后该变量不存在了。
char *str = "hello world";是指向了常量区的字符串,位于静态存储区,它在程序生命期内恒定不变,所以字符串还在。无论什么时候调用 strA,它返回的始终是同一个“只读”的内存块。
它返回的始终是同一个“只读”的内存块。重要的事情说三遍!它返回的始终是同一个“只读”的内存块。
另外想要修改上述字符数组变量的不合理分配,也可以这样:

char *strA()
{
    static char str[] = "hello world";
    return str;
}
通过static开辟一段静态存贮空间。
答案:
因为这个函数返回的是局部变量的地址,当调用这个函数后,这个局部变量str就释放了,所以返回的结果是不确定的且不安全,随时都有被收回的可能。
【总之,返回值不能是数组,最好是指针!】


【另】

char *str = “hello”;
// 那么:*str = “h”;str = “hello”;&str = (地址)
【另】
char *a[ ] = { "hello","the","world"};
char **ptra = a;
// ptra++; cout<< *ptra << endl; // 输出 为 the
// cout<< *ptra++ << endl; // 输出 为 hello
【换言之,‘*’的优先级高于‘++’】
字符串 和 数组的理解分析 详见 深入理解 字符串 和 数组;
MSBD.V.4.0 - P.69.3 ~ 70.5 ~ 72.7 ~ 73.9- IMPT !!!


内存偏移
内存中两个指针 ptra 和 ptrb,其中,(ptra - ptrb)的值不是实际地址的数学差。(假设 ptra 和 ptrb 指向整型变量)实质上它们在编译其中的运算为(ptra - ptrb)/sizeof(int)。
偏移地址,代码示例 01

#include ...
class A{ ... };
class B{ ... };
int main( )
{
      A a;
      B *pb = (B*)(&a);
}
【解析】
这里将对象 a 的地址赋给了一个指向 B 类对象的指针,这样的赋值是很野蛮的。虽然,不一定报错,造成的后果是强制把 a 地址内容看成是一个 B 类对象,pb 指向的是 a 类的内存空间。
赋值和继承 导致 成员变量数值传递 的 比较分析 
详见:C++ 类继承与对象赋值 情况下 成员变量的覆盖 浅析
详址:http://blog.csdn.net/u013630349/article/details/46722893

偏移地址,代码示例 02:

#include ...
int main( )
{
      int *ptr;
      ptr = (int*)0x8000;
      *ptr = oxaabb;
}
【解析】
代码的运行将会报错。这段代码的本意是将 0x8000 内存空间 指向一个 整型数据的地址。其中,0x8000 指向的空间存储 oxaa ,0x8001 指向的空间存储 oxbb 。指针随机分配地址是不被允许的,除非所有的资源为程序员操作,否则,尽量避免操作底层。
偏移地址,代码示例 03:

#include
using namespace std;
struct S
{
	int i;
	int *p;
}
main( )
{
	S s;
	int *p = &s.i;
	p[0] = 1;
	p[1] = 5;

	cout<<"s 的地址 \t"<<&s<【代码输出】 
  
s 的地址        0018FF40
s.i 的地址      0018FF40
p 的值           0018FF40
p[0] 的地址   0018FF40
s.p 的地址     0018FF44
p[1] 的地址   0018FF44
s.i 的值         1
s.p 的值        00000005
*s.p 的值	     报错
s.*p 的值      报错
&s.*p 的值   报错
************************
执行s.p = p;    之后
************************
s 的地址        0018FF40
s.i 的地址      0018FF40
p 的值          0018FF40
p[0] 的地址     0018FF40
s.p 的值        0018FF40
s.p 的地址      0018FF44
s.p[0] 的值     1
s.p[1] 的值     1638208
p[1] 的地址     0018FF44
p[0] 的值       1
p[1] 的值       1638208
************************
执行s.p[1] = 1; 之后
************************
s.p = 00000001
s.p+1 = 00000005
Press any key to continue
【代码分析】
int *p = &s.i;
// 等价于 p = &p[0] = &s = &s.i
// 且还有 p+1 = &p[1] = &s+1 = &s.i+1 = &s.p
s.p = p;
// 要知道 (s.*p) = s.p = p
// 等价有 *(p+1) = *&p[1] = *&s+1 = *&s.i+1 = *&s.p = p
// 简化得 *(p+1) = p[1] = s+1 = s.i+1 = s.p = p
// 已知道 p = &p[0] = s.p = &s.p[0]
// 所以有 &p[0] = p[1] 和 &s.p[0] = s.p[1]
s.p[1] = 1
// 此时再给,s.p[0]或者p[0]赋值,等价于在内存地址为 1 的空间写入变量。
// 所以,此句暂时不会报错,一旦对指向地址赋值,就会导致程序崩溃。
考点:各种类型的指针表示
优先级
()> 数据类型(如: int 、 char etc.)> [ ] > * > ++
函数指针 void (*ptr)();
函数返回指针 int *func();
const 指针 const int *ptr;
指向 const 的指针 int *const ptr;
指向 const 的 const 指针 const int *const ptr;
一个有10个指针的数组,指针指向一个函数,该函数有一个整形参数 并返回一个整型
int ( *ptr ( int ))[10]
int (*(*ptr)(int,int))(int)
ptr是一个输入参数为两个整型变量的函数指针,其返回值是一个输入参数为一个整形变量返回值为整型的的函数指针


函数指针声明方式
...
int max(int,int);// 已有求最大值函数 
...

int *ptr(int,int) = &max;

详见:

C++ 高维指针数组 与 高维数组指针(一)

C++ 高维指针数组 与 高维数组指针(二)

C++ 数组指针 指针数组 以及 函数指针 指针函数

详址:

http://blog.csdn.net/u013630349/article/details/44998689

http://blog.csdn.net/u013630349/article/details/44195523

http://blog.csdn.net/u013630349/article/details/45098899


【代码示例】

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

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

printf("%d %d", *(a+1), *(ptr-1));

说明:*(a+1) 直接就为2 简单&a+1   由于本身a就为数组名也就是指针,加上& 相当于双指针 也就相当于**(a+1) 所以加1 就是数组整体加一行,ptr指向a的第6个元素,所以*(ptr-1)为5

详见:C++ 数组指针 指针数组 以及 函数指针 指针函数

详址:http://blog.csdn.net/u013630349/article/details/45098899


考点:空指针和悬浮指针
空指针为申请了内存空间,但是,指向为 NULL 或者 0 的指针,即 ptr = 0 ,此时,对指针操作虽然会导致崩溃,但,调试起来比悬浮指针方便许多;

悬浮指针为申请了内存,经过一系列操作,释放内存后的指针。指针虽不再指向固定内存,但是,指针还是存在,且指向随机区域,此时对指针操作十分危险的。


考点:malloc/free 与 new/delete

malloc free new delete 的区别、比较、分析 

详见:C++ 中 malloc/free 与 new/delete 浅析

详址:http://blog.csdn.net/u013630349/article/details/44947255


考点:指针与句柄
Windows采用句柄标记系统资源,隐藏系统信息。你只需要知道有这个东西,然后去调用就好了,句柄是一个32位无符号整型数。
指针则是标记某个物理内存地址。

auto_ptr
std::auto_ptr pObj(new Object);
auto_ptr 好处在于在析构时会自动删除此指针,但是不要误用;
1)不能共享所有权,即不要让两个auto_ptr指向同一个对象;
2)不能指向数组,因为在析构时候调用delete,而不是delete[];
3)不能作为容器的对象;

考点:this 指针

详见:C++ this 指针 浅析

详址:http://blog.csdn.net/u013630349/article/details/46412485

C++的编译系统只用了一段空间来存放在公共函数代码,在调用各个对象的成员函数时,都要调用这个公共的函数代码
this指针的值为当前调用成员函数的对象的起始地址。
一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时使用this指针,如this->num = num (不能写成num = num)。
this指针只能在成员函数中使用。全局函数,静态函数都不能使用this。 成员函数默认第一个参数为 T* const this。
this是通过函数的首参来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间,类的函数在定义完成后,它就在那儿,不会跑的。   


遗留问题:
【野指针】
句柄
智能指针

你可能感兴趣的:(MSBD小结,指针,引用,数组,句柄,new)