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 <iostream>
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<iostream>
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<<endl;
cout<<"s.i 的地址 \t"<<&s.i<<endl;
cout<<"p 的值 \t\t"<<p<<endl;
cout<<"p[0] 的地址 \t"<<&p[0]<<endl;
cout<<"s.p 的地址 \t"<<&s.p<<endl;
cout<<"p[1] 的地址 \t"<<&p[1]<<endl;
cout<<"s.i 的值 \t"<<s.i<<endl;
cout<<"s.p 的值 \t"<<s.p<<endl;
cout<<"*s.p 的值 \t"<<"报错"<<endl;
cout<<"s.*p 的值 \t"<<"报错"<<endl;
cout<<"&s.*p 的值 \t"<<"报错"<<endl; s.p = p;
cout<<"************************"<<endl;
cout<<"运行s.p = p; \t"<<"之后"<<endl;
cout<<"************************"<<endl;
cout<<"s 的地址 \t"<<&s<<endl;
cout<<"s.i 的地址 \t"<<&s.i<<endl;
cout<<"p 的值 \t\t"<<p<<endl;
cout<<"p[0] 的地址 \t"<<&p[0]<<endl;
cout<<"s.p 的值 \t"<<s.p<<endl;
cout<<"s.p 的地址 \t"<<&s.p<<endl;
cout<<"s.p[0] 的值 \t"<<s.p[0]<<endl;
cout<<"s.p[1] 的值 \t"<<s.p[1]<<endl;
cout<<"p[1] 的地址 \t"<<&p[1]<<endl;
cout<<"p[0] 的值 \t"<<p[0]<<endl;
cout<<"p[1] 的值 \t"<<p[1]<<endl; s.p[1] = 1;
cout<<"************************"<<endl;
cout<<"运行s.p[1] = 1;\t"<<"之后"<<endl;
cout<<"************************"<<endl;
cout<<"s.p = "<<s.p<<endl;
cout<<"s.p+1 = "<<s.p+1<<endl;
// cout<<"*s.p = "<<*s.p<<endl; // 崩溃
}
【代码输出】
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;
详见:
详址:
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
详址: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 <Object> pObj(new Object);
auto_ptr 优点在于在析构时会自己主动删除此指针,可是不要误用;
1)不能共享全部权。即不要让两个auto_ptr指向同一个对象;
2)不能指向数组,由于在析构时候调用delete,而不是delete[];
3)不能作为容器的对象;
考点: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指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,仅仅分配类中的变量空间。并没有为函数分配空间,类的函数在定义完毕后。它就在那儿,不会跑的。
遗留问题:
【野指针】
句柄
智能指针
C++ 指针与引用 知识点 小结的更多相关文章
- C++重要知识点小结---3
C++重要知识点小结---1:http://www.cnblogs.com/heyonggang/p/3246631.html C++重要知识点小结---2:http://www.cnblogs.co ...
- C++重要知识点小结---2
C++重要知识点小结--1 :http://www.cnblogs.com/heyonggang/p/3246631.html 1.C++允许程序员声明一个不能有实例对象的类,这样的类惟一的用途是被继 ...
- “段错误(segment fault)”、“非法操作,该内存地址不能read/write” 非法指针解引用造成的错误。
小结: 1. “段错误(segment fault)”.“非法操作,该内存地址不能read/write”非法指针解引用造成的错误. <程序员的自我修养 : 链接.装载与库> Q 我写的程序 ...
- 段错误 “段错误(segment fault)”、“非法操作,该内存地址不能read/write” 非法指针解引用造成的错误。
[root@test after_fc_distributed]# ./ffmpeg-linux64-v3.3.1 -i "concat:mymp3tmp/test_0.mp3|mymp3t ...
- SpringBoot 系列教程之事务隔离级别知识点小结
SpringBoot 系列教程之事务隔离级别知识点小结 上一篇博文介绍了声明式事务@Transactional的简单使用姿势,最文章的最后给出了这个注解的多个属性,本文将着重放在事务隔离级别的知识点上 ...
- [速记]关于指针,引用和递归和解递归——C++
在写基于二叉排序树的查找时,分为三个过程 1.二叉排序树的插入 2.二叉排序树的建立 3.基于二叉排序树的查找 其中第三部可以递归方式实现,也可以用while循环解递归,于是我想也解解第一步的递归,看 ...
- C++指针参数引用
粘个代码占位置,以后有时间把指针函数,函数指针都补上 #include <iostream> using namespace std; void freePtr1(int* p1){ /* ...
- C/C++:提升_指针的指针和指针的引用
C/C++:提升_指针的指针和指针的引用 写在前面 今天在使用指针的时候我发现了一个自己的错误.
- C++_系列自学课程_第_8_课_指针和引用_《C++ Primer 第四版》
C语言最富有迷幻色彩的部分当属指针部分,无论是指针的定义还是指针的意义都可算是C语言中最复杂的内容.指针不但提供给了程序员直接操作硬件部分的操作接口,还提供给了程序员更多灵活的用法.C++继承这一高效 ...
随机推荐
- linux添加开机启动项的方法介绍
使用chkconfig命令可以查看在不同启动级别下课自动启动的服务(或是程序),命令格式如下:chkconfig --list可能输出如下:openvpn 0:关闭 1:开启 ...... 6:关闭 ...
- 解决MyEclipse中安装或升级ADT之后SDK Target无法显示的问题
故障现象,在MyEclipse里面安装完最新的android sdk和ADT之后,无法新建项目,Build Target为空,显示一直在loading.即如下面图里面显示的,Target Na ...
- lsblk---列出所有可用块设备的信息,
lsblk命令用于列出所有可用块设备的信息,而且还能显示他们之间的依赖关系,但是它不会列出RAM盘的信息.块设备有硬盘,闪存盘,cd-ROM等等.lsblk命令包含在util-linux-ng包中,现 ...
- JS数组去重的6种算法实现
1.遍历数组法 最简单的去重方法,实现思路:新建一新数组,遍历传入数组,值不在新数组就加入该新数组中:注意点:判断值是否在数组的方法"indexOf"是ECMAScript5 方法 ...
- XTUOJ 1238 Segment Tree
Segment Tree Accepted : 3 Submit : 21Time Limit : 9000 MS Memory Limit : 65536 KB Problem Descriptio ...
- 【剑指Offer学习】【面试题27:二叉搜索树与双向链表】
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建不论什么新的结点.仅仅能调整树中结点指针的指向. 比方输入图4.12 中左边的二叉搜索树,则输出转换之后的排序现向链表. ...
- 【剑指offer】Q25:二叉树中和为某一值的路径
说明:最烦的就是看别人的博客,题解里直接上代码,一行分析都没有.只是这个题... class BTNode(): def __init__(self, val = -1): self.val = va ...
- 设计模式实例(Lua)笔记之六(Adapter模式)
1.描写叙述 "我"在 2004 年的时候带了一个项目,做一个人力资源管理,该项目是我们总公司发起的项目,公司一共同拥有 700 多号人,包含子公司,这个项目还是比較简单的,分为三 ...
- microsoft SQL server,错误2
大二下開始学习数据库,一開始就把数据库装了,结果数据库第一节实验课就是教我们装数据库,而且要在自己机子上装,还要实验报告和截图.老师叫我把原本的卸载掉, 于是对着网上一系列的操作卸载server删除目 ...
- 71.用express框架,出现 express.Router is not a function
Express版本太久