C++面试

目录

1.指针常量和常量指针的区别

指针常量和常量指针是两种不同的概念,具有不同的含义。

2.虚函数表指针一般存在对象的什么位置

3.虚拟内存相关详细讲一下

4.讲讲左值和右值

5.什么时候使用右值

6.假如 a 是 T 的左值引用,T 是 int&& 类型的,那么 a 实际上是什么


1.指针常量和常量指针的区别

指针常量和常量指针是两种不同的概念,具有不同的含义。

指针常量是指一个指针变量被声明为常量,而指针所指向的内存区域是可以改变的。也就是说,指针常量本身的值是不可以修改的,但是通过指针变量所指向的内存地址可以进行修改。例如:

int a = 10;
int b = 20;
int * const p = &a;
*p = 30; // 将 a 的值修改为 30
p = &b; // 编译时会报错,因为 p 是指针常量,其值不可修改

常量指针是指一个指针变量被声明为指向常量的指针,也就是指针所指向的内存区域是不可以改变的。也就是说,常量指针所指向的内存地址是不可修改的,但是可以通过指针变量来读取该地址上存储的值。例如:

int a = 10;
int b = 20;
const int* p = &a;
// *p = 30; // 编译时会报错,因为 p 是指向常量的指针,其指向的内存区域不可修改
p = &b; // 通过指针变量可以修改指针的值,p 现在指向了 b 的地址

需要注意的是,在指针常量和常量指针中,const 关键字的位置不同,意义也不同。在指针常量中,const 修饰的是指针本身,而在常量指针中,const 修饰的是指针所指向的内存区域。

2.虚函数表指针一般存在对象的什么位置

虚函数表指针(vptr)是 C++ 中实现动态多态性的关键之一,它是一个指向虚函数表的指针。虚函数表是一个包含了该类中所有虚函数的地址的数组,当对象被创建时,vptr 就会被初始化为指向该类的虚函数表。

在大多数的 C++ 实现中,vptr 被放在对象的内存布局的最开始处或最结束处,其具体位置由具体编译器的实现和ABI(应用程序二进制接口)规范决定。一些编译器甚至可以将 vptr 放在对象的任意位置。

因此,如果需要在代码中访问对象的 vptr,就需要使用编译器提供的指定特定编译器的语法或者按照ABI文档中规定的规则来访问内存中的数据。但一般来说,在对象的使用上,程序员无需直接访问 vptr。

3.虚拟内存相关详细讲一下

虚拟内存是一种计算机内存管理技术,它使得应用程序可以访问比实际物理内存更大的内存空间。虚拟内存将进程所需的内存空间划分为多个大小相等的页面(page),并将这些页面映射到物理内存或者硬盘上的交换区(swap area)中。

当一个进程需要访问内存时,操作系统会将这个页面加载到物理内存或者缓存中,如果物理内存已经占满,则优先将长时间未使用的页面置换到交换区中。当程序试图访问一个已经被置换出去的页面时,操作系统会将其重新加载到物理内存或者缓存中。这个过程对于应用程序来说是透明的,即应用程序可以像访问物理内存一样访问虚拟内存,而无需关心物理内存和交换区之间的映射关系。

虚拟内存的实现可以提供如下几个重要的好处:

  1. 扩大进程可寻址的内存空间,一个进程可以访问比物理内存更大的内存空间。

  2. 提高内存使用的效率,只有在需要时才将数据从磁盘读取到物理内存中,避免了浪费物理内存的情况。

  3. 提高系统的稳定性,当物理内存不足时,操作系统可以自动将部分进程的内存页调度到交换区中,从而避免系统崩溃。

  4. 增加了内存保护的功能,即在每个进程的地址空间中,操作系统将不同的对象(代码、数据、堆栈等)置于不同的页面中,从而增加了安全性和保护性,防止恶意程序进行越界访问等行为。

需要注意的是,虚拟内存的实现也存在一定的开销,例如处理页面失效、磁盘读写等过程所需的时间和性能开销。因此,在使用虚拟内存时,需要针对具体应用场景进行优化和调整,以提高系统的效率和稳定性。

4.讲讲左值和右值

在C++中,表达式包括左值和右值。简单来说,左值是指可以被取地址的表达式,而右值则是指不能被取地址的表达式。

具体地说,左值可以作为赋值语句的左侧,例如:

int x = 1; // x是左值 x = 2; // x是左值

而右值则不能作为赋值语句的左侧,例如:

int x = 1; 2 = x; // 错误!2是右值,不能作为赋值语句的左侧

另外,可以通过&运算符获取左值的地址,例如:

int x = 1; int* p = &x; // 取x的地址,p指向x,x是左值

而右值不能取地址,例如:

int a, b; int* p1 = &(a + b); // 错误!a+b是右值,不能取其地址 int* p2 = &3; // 错误!3是右值,不能取其地址

总结一下,左值是可以被取地址的表达式,可以作为赋值语句的左侧;右值则不能被取地址,不能作为赋值语句的左侧。需要注意的是,在使用C++11及以上版本时,引入了“右值引用”的概念,即可以将右值绑定到一个引用上,并且在函数传参等场景中引入右值引用可以提高程序效率。

5.什么时候使用右值

在C++中,右值的主要作用是提高程序的性能效率。当我们需要创建临时对象或者获取一个表达式计算结果时,使用右值可以避免不必要的内存拷贝和对象创建操作,从而提高程序的执行速度。

具体来说,可以通过将右值绑定到一个右值引用上来实现这个目的,例如:

void func(string&& s) { // ... } int main() { string s1 = "hello"; func(move(s1)); // 将s1转化为右值并传入函数中,避免创建新的对象 }

另外,在使用STL容器和算法等场景中,右值也常常被用来传递或返回某个类型的临时对象,例如:

vector<int> v = {1, 2, 3}; auto iter = find(begin(v), end(v), 2); // find函数返回右值迭代器

需要注意的是,在使用右值时需要谨慎处理资源的所有权和生命周期,避免出现内存泄漏和资源竞争等问题。同时,要确保代码的可读性和可维护性,在使用右值时应该考虑清楚其用途、使用场景和风险,避免滥用和误用。

6.假如 a 是 T 的左值引用,T 是 int&& 类型的,那么 a 实际上是什么

如果a是T的左值引用而T是int&&类型的,那么a实际上也是一个int&&类型的左值引用。

因为左值引用可以绑定到同类型或同类型的派生类的左值上,而且int&&类型是一个右值引用类型,但是当右值引用绑定到左值时会变成左值引用。因此,当将int&&类型的变量绑定到左值引用时,左值引用会继承原始类型(int&&)的右值属性,同时又成为了一个左值,因而可以进行地址取值等操作。

举个例子,如下所示:

int a = 10; int&& b = std::move(a); // 将变量a转换为右值引用类型 int&& c = b; // c是b的左值引用,但是类型依然是int&&

在上面的代码中,首先将变量a转换成右值引用类型,并将其赋值给变量b。然后,将b赋值给变量c,由于b是一个左值,因此c实际上也是b的左值引用,但是由于b的类型是int&&,因此c也是int&&类型的左值引用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值