欢迎关注
0voice · GitHub
11
、指针和引⽤的区别
指针和引⽤都是⼀种内存地址的概念,区别呢,指针是⼀个实体,引⽤只是⼀个别名。
在程序编译的时候,将指针和引⽤添加到符号表中。
指针它指向⼀块内存,指针的内容是所指向的内存的地址,在编译的时候,则是将
“
指针变ᰁ名
-
指针变ᰁ的地址
”
添
加到符号表中,所以说,指针包含的内容是可以改变的,允许拷⻉和赋值,有
const
和⾮
const
区别,甚⾄可以为
空,
sizeof
指针得到的是指针类型的⼤⼩。
⽽对于引⽤来说,它只是⼀块内存的别名,在添加到符号表的时候,是将
"
引⽤变ᰁ名
-
引⽤对象的地址
"
添加到符号
表中,符号表⼀经完成不能改变,所以引⽤必须⽽且只能在定义时被绑定到⼀块内存上,后续不能更改,也不能为
空,也没有
const
和⾮
const
区别。
sizeof
引⽤得到代表对象的⼤⼩。⽽
sizeof
指针得到的是指针本身的⼤⼩。另外在参数传递中,指针需要被解引⽤
后才可以对对象进⾏操作,⽽直接对引⽤进⾏的修改会直接作⽤到引⽤对象上。
作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引⽤的实质是传地址,传递的是变ᰁ的地址
12
、 ᰀ
(wild)
指针与悬空
(dangling)
指针有什么区别?如何避免?
ᰀ指针
(wild pointer)
:就是没有被初始化过的指针。⽤
gcc -Wall
编译
,
会出现
used uninitialized
警告。
悬空指针:是指针最初指向的内存已经被释放了的⼀种指针。
⽆论是ᰀ指针还是悬空指针,都是
指向⽆效内存区域
(
这⾥的⽆效指的是
"
不安全不可控
")
的指针
。 访问
"
不安全可
控
"(invalid)
的内存区域将导致
"Undefined Behavior"
。
如何避免使⽤ᰀ指针?
在平时的编码中,养成在定义指针后且在使⽤之前完成
初始化
的习惯或者使⽤智能指针。
13
、说⼀下
const
修饰指针如何区分?
下⾯都是合法的声明,但是含义⼤不同:
const int * p1; //
指向
整形常ᰁ
的指针,它指向的值不能修改
int * const p2; //
指向整形的
常ᰁ指针
,它不能在指向别的变ᰁ,但指向(变ᰁ)的值可以修改。
const int *const p3; //
指向
整形常ᰁ
的
常ᰁ指针
。它既不能再指向别的常ᰁ,指向的值也不能修改。
理解这些声明的技巧在于,查看关键字
const
右边来确定什么被声明为常ᰁ ,如果该关键字的右边是类型,则值是
常ᰁ;如果关键字的右边是指针变ᰁ,则指针本身是常ᰁ。
14
、简单说⼀下函数指针
从定义和⽤途两⽅⾯来说⼀下⾃⼰的理解:
⾸先是定义:函数指针是指向函数的指针变ᰁ。函数指针本身⾸先是⼀个指针变ᰁ,该指针变ᰁ指向⼀个具体的函
数。这正如⽤指针变ᰁ可指向整型变ᰁ、字符型、数组⼀样,这⾥是指向函数。
在编译时,每⼀个函数都有⼀个⼊⼝地址,该⼊⼝地址就是函数指针所指向的地址。有了指向函数的指针变ᰁ后,
可⽤该指针变ᰁ调⽤函数,就如同⽤指针变ᰁ可引⽤其他类型变ᰁ⼀样,在这些概念上是⼤体⼀致的。
其次是⽤途:调⽤函数和做函数的参数,⽐如回调函数。
示例:
15
、堆和栈区别
栈
由编译器进⾏管理,在需要时由编译器⾃动分配空间,在不需要时候⾃动回收空间,⼀般保存的是局部变ᰁ和函数
参数等。
连续的内存空间,在函数调⽤的时候,⾸先⼊栈的主函数的下⼀条可执⾏指令的地址,然后是函数的各个参数。
⼤多数编译器中,参数是从右向左⼊栈(原因在于采⽤这种顺序,是为了让程序员在使⽤
C/C++
的
“
函数参数⻓度可
变
”
这个特性时更⽅便。如果是从左向右压栈,第⼀个参数(即描述可变参数表各变ᰁ类型的那个参数)将被放在
栈底,由于可变参的函数第⼀步就需要解析可变参数表的各参数类型,即第⼀步就需要得到上述参数,因此,将它
放在栈底是很不⽅便的。)本次函数调⽤结束时,局部变ᰁ先出栈,然后是参数,最后是栈顶指针最开始存放的地
址,程序由该点继续运⾏,不会产⽣碎⽚。
栈是⾼地址向低地址扩展,栈低⾼地址,空间较⼩。
堆
由程序员管理,需要⼿动
new malloc delete free
进⾏分配和回收,如果不进⾏回收的话,会造成内存泄漏的问
题。
不连续的空间,实际上系统中有⼀个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第⼀个⼤于等于申请
⼤⼩的空间分配给程序,⼀般在分配程序的时候,也会空间头部写⼊内存⼤⼩,⽅便
delete
回收空间⼤⼩。当然
如果有剩余的,也会将剩余的插⼊到空闲链表中,这也是产⽣内存碎⽚的原因。
堆是低地址向⾼地址扩展,空间交⼤,较为灵活。
16
、函数传递参数的⼏种⽅式
值传递:
形参是实参的拷⻉,函数内部对形参的操作并不会影响到外部的实参。
指针传递:
也是值传递的⼀种⽅式,形参是指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进
⾏操作。
引⽤传递:
实际上就是把引⽤对象的地址放在了开辟的栈空间中,函数内部对形参的任何操作可以直接映射到外部
的实参上⾯。
17
、
new / delete
,
malloc / free
区别
都可以⽤来在堆上分配和回收空间。
new /delete
是操作符,
malloc/free
是库函数。
执⾏
new
实际上执⾏两个过程
:
1.
分配未初始化的内存空间(
malloc
);
2.
使⽤对象的构造函数对空间进⾏初始
化;返回空间的⾸地址。如果在第⼀步分配空间中出现问题,则抛出
std::bad_alloc
异常,或被某个设定的异常处
理函数捕获处理;如果在第⼆步构造对象时出现异常,则⾃动调⽤
delete
释放内存。
执⾏
delete
实际上也有两个过程
:
1.
使⽤析构函数对对象进⾏析构;
2.
回收内存空间(
free
)。
以上也可以看出
new
和
malloc
的区别,
new
得到的是经过初始化的空间,⽽
malloc
得到的是未初始化的空间。
所以
new
是
new
⼀个类型,⽽
malloc
则是
malloc
⼀个字节⻓度的空间。
delete
和
free
同理,
delete
不仅释放
空间还析构对象,
delete
⼀个类型,
free
⼀个字节⻓度的空间。
为什么有了
malloc
/
free
还需要
new
/
delete
?
因为对于⾮内部数据类型⽽⾔,光⽤
malloc
/
free
⽆法满⾜动
态对象的要求。对象在创建的同时需要⾃动执⾏构造函数,对象在消亡以前要⾃动执⾏析构函数。由于
mallo
/
free
是库函数⽽不是运算符,不在编译器控制权限之内,不能够把执⾏的构造函数和析构函数的任务强加于
malloc
/
free
,所以有了
new
/
delete
操作符。