参考:https://www.runoob.com/cplusplus/cpp-tutorial.html
本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。
目录
第三章 指针和引用
3.1 指针
3.1.1 指针定义
指针是一个变量,其值为另一个变量的内存地址。指针变量声明的一般形式为:
type *var-name;
int *ip; /* 一个整型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
3.1.2 指针的使用
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
其结果为:
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
3.1.3 常用指针操作
//空指针
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ; //结果是:ptr 的值是 0
//指针递增
int var[3] = {10, 100, 200};
ptr = var; //数组的变量名代表指向第一个元素的指针
ptr++;
//指向指针的指针
int var;
int *ptr;
int **pptr;
var = 3000;
// 获取 var 的地址
ptr = &var;
// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;
3.2 引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
3.3 相关面试题
Q:C/C++ 中指针和引用的区别?
A:
- 指针有自己的一块空间,而引用只是一个别名;
- 指针可以被初始化为NULL,而引用必须被初始化;
- 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
- 指针可以有多级指针(**p),而引用只有一级;
Q:指针函数和函数指针?
A:
- 指针函数本质上是一个函数,函数的返回值是一个指针;
- 函数指针本质上是一个指针,C++在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,有了函数指针后,就可用该指针变量调用函数。
char * fun(char * p) {…} // 指针函数fun char * (*pf)(char * p); // 函数指针pf pf = fun; // 函数指针pf指向函数fun pf(p); // 通过函数指针pf调用函数fun
Q:在什么时候需要使用“常引用”?
A:如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
Q:C++中的四个智能指针: shared_ptr、unique_ptr、weak_ptr、auto_ptr
A:智能指针出现的原因:智能指针的作用就是用来管理一个指针,将普通的指针封装成一个栈对象,当栈对象的生命周期结束之后,会自动调用析构函数释放掉申请的内存空间,从而防止内存泄露。(https://www.cnblogs.com/WindSun/p/11444429.html)
- shared_ptr实现共享式拥有概念。多个智能指针指向相同对象,该对象和其相关资源会在最后一个引用被销毁时被释放。
- unique_ptr实现独占式拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
- weak_ptr 是一种共享但不拥有对象的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段,它的构造和析构不会引起引用计数的增加或减少。weak_ptr 设计的目的是为协助 shared_ptr工作的,用来解决shared_ptr相互引用时的死锁问题。注意的是我们不能通过weak_ptr直接访问对象的方法,以通过调用lock函数来获得shared_ptr,再通过shared_ptr去调用对象的方法。
- auto_ptr采用所有权模式,C++11中已经抛弃。
Q:shared_ptr的底层实现
A:
template <typename T> class smart_ptrs { public: smart_ptrs(T*); //用普通指针初始化智能指针 smart_ptrs(smart_ptrs&); // 拷贝构造 T* operator->(); //自定义指针运算符 T& operator*(); //自定义解引用运算符 smart_ptrs& operator=(smart_ptrs&); //自定义赋值运算符 ~smart_ptrs(); //自定义析构函数 private: int *count; //引用计数 T *p; //智能指针底层保管的指针 }; //构造函数 template <typename T> smart_ptrs<T>::smart_ptrs(T *p): count(new int(1)), p(p) {} //对普通指针进行拷贝,同时引用计数器加1,因为需要对参数进行修改,所以没有将参数声明为const template <typename T> smart_ptrs<T>::smart_ptrs(smart_ptrs &sp): count(&(++*sp.count)), p(sp.p) {} //指针运算符 template <typename T> T* smart_ptrs<T>::operator->() {return p;} //定义解引用运算符 template <typename T> T& smart_ptrs<T>::operator*() {return *p;} //定义赋值运算符,左边的指针计数减1,右边指针计数加1,当左边指针计数为0时,释放内存: template <typename T> smart_ptrs<T>& smart_ptrs<T>::operator=(smart_ptrs& sp) { ++*sp.count; if (--*count == 0) { //自我赋值同样能保持正确 delete count; delete p; } this->p = sp.p; this->count = sp.count; return *this; } // 定义析构函数: template <typename T> smart_ptrs<T>::~smart_ptrs() { if (--*count == 0) { delete count; delete p; } }
Q:野指针
A:野指针就是指向一个已销毁或者访问受限内存区域的指针。产生野指针通常是因为几种疏忽:
- 指针变量未被初始化;
- 指针释放后未置空;
- 指针操作超越变量作用域(例如变量被释放了,指针还是指向它)。
Q:什么时候会发生段错误?
A:段错误通常发生在访问非法内存地址的时候,具体来说分为以下几种情况:
- 使用了野指针
- 试图修改字符串常量的内容
- 数组越界导致栈溢出Q:什么是右值引用,跟左值又有什么区别?
A:左值:能对表达式取地址的具名对象/变量等。一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址的字面量、函数返回值、匿名函数或匿名对象。一般指表达式结束就不再存在的临时对象。
右值引用和左值引用的区别在于:
- 通过&获得左值引用,左值引用只能绑定左值。
- 通过&&获得右值引用,右值引用只能绑定右值,基于右值引用可以实现移动语义和完美转发,右值引用的好处是减少右值作为参数传递时的复制开销,提高效率。
Q:什么是std::move()以及什么时候使用它?
A:std::move()是C ++标准库中用于转换为右值引用的函数。当需要在其他地方“传输”对象的内容时使用std :: move,对象可以在不进行复制的情况下获取临时对象的内容,避免不必要的深拷贝。
Q:C++类的内部可以定义引用数据成员吗?
A:可以,必须通过成员函数初始化列表初始化
class MyClass { public: MyClass(int &i): a(1), b(i){ // 构造函数初始化列表中是初始化工作 // 在这里做的是赋值而非初始化工作 } private: const int a; int &b; // 引用数据成员b,必须通过列表初始化! };