C++基础知识(11)

71、深拷贝与浅拷贝有什么区别?
浅拷贝只能复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存;而深拷贝会创造一个相同的对象,新对象与源对象不共享内存,修改新对象并不会影响源对象。

72、在C++中,使用malloc申请的内存能否通过delete释放?使用new 申请的内存能否用free释放?

不可以,因为malloc/free,new/delete必须配对使用。malloc和free是C/C++的标准库函数,而new和delete是C++的运算符,两者都可以动态申请内存和释放内存。对于非内部数据类型,光用malloc和free是无法满足动态对象的要求,因为一个对象的创建需要调用构造函数,对象的消亡需要调用析构函数,而malloc和free是不会调用这些函数的,因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。由于内部数据类型的对象没有构造和析构的过程,理论上malloc/free和new/delete是等价的,但一般不会混用。另外,虽然new/delete已经覆盖了malloc/free的所有功能,但是由于C++经常要调用C函数,所以为了兼容性,保留了malloc/free。

详细可以看这篇博客:https://blog.csdn.net/Dream_xun/article/details/50347139

73、内存块太小导致malloc和new返回空指针,该怎么处理?

对于malloc来说,需要判断其是否返回空指针,如果是则马上用return语句终止该函数或者exit终止该程序;

对于new来说,默认抛出异常,所以可以使用try…catch…代码块的方式:

try{
    int* ptr = new int[10000000];
}catch(bad_alloc &memExp){
	cerr<<memExp.what()<<endl;
}

还可以使用set_new_handler函数的方式:

void no_more_memory(){
	cerr<<"Unable to satisfy request for memory"<<endl;
	abort();
}
int main()
{
	set_new_handler(no_more_memory);
	int* ptr = new int[10000000];
}

在这种方式里,如果new不能满足内存分配要求,no_more_memory会反复被调用,所以new_handler函数必须完成以下事情:

(1)让更多内存可以被使用:可以在程序一开始执行就分配一大块内存,之后当new_handler第一次被调用,就将这些内存释放还给程序使用;

(2)使用另一个new_handler;

(3)卸除new_handler:返回空指针,这样new就会抛出异常、

(4)直接抛出bad_alloc异常

(5)调用abort或者exit

74、结构体的字节对齐

结构体作为一种复合数据类型,其构成元素既可以是基本是数据类型的变量,也可以是一些复合类型数据。对此编译器会自动进行成员变量的对齐以提高运算效率。默认情况下,按声明顺序在内存顺序存储,第一个成员的地址和整个结构的地址相同,向结构体成员中size最大的成员对齐。一般来说结构体的size可以为1,2,4,8,12,16,20…

(除了1,2后面的基本都是4和8的倍数)

#include<iostream>

struct S1
{
    // 后面加冒号是代表按位存储,称为“位域”
    int i:8;
    char j:4;
    int a:4;
    double b;
};

struct S2
{
    int i:8;
    char j:4;
    double b;
    int a:4;
};

struct S3
{
    int i;
    char j;
    double b;
    int a;
};

int main()
{
    std::cout<<"sizeof(S1): "<<sizeof(S1)<<endl;
    std::cout<<"sizeof(S2): "<<sizeof(S2)<<endl;
    std::cout<<"sizeof(S3): "<<sizeof(S3)<<endl;
    return 0;
}

/*
	输出结果:
	sizeof(S1): 16
	sizeof(S1): 24
	sizeof(S1): 24
*/

(1)第一种情况:

在S1中,如果不看最后面的double,那么int类型是占4个字节,有32位,int i:8占8位; char j:4占4位;int a:4占4位,所以32位并没有存放完,此时sizeof(S1)应该为4;但是后面增加double,double类型是8字节,所以整体对齐后,前面三个变量用8字节存放,后面都double也用8字节存放,因此S1的size为16字节。

(2)第二种情况

与第一种情况不同的是调换了第三个变量和第四个变量的顺序;前两个变量原本可以用4字节存放,但是由于向第三个变量double类型对齐,整体变为8字节,也就是说前两个变量用8字节存放,第三个第四个都分别用8字节存放,因此S2的size为24字节

(3)第三种情况

与前两种情况不一样,结构体里面的成员变量都不用位域来存放,在32位机器下,int类型是4个字节,char类型1个字节,double8个字节,跟情况2一样向8字节对齐,那么8字节可以存放下第一个变量和第二个变量,所以S3的size就是24字节。

75、什么是右值引用?

C++11为了支持移动操作,新引入的一种引用类型。所谓右值引用就是必须绑定在右值的引用,用&&来获取右值引用。右值引用有一个重要的性质:只能绑定到一个将要销毁的对象,基于这个性质,就可以自由地将一个右值引用的资源移动到另一个对象。

int i = 2;
int &r = i; // 正确,这是左值引用
int &&rr = i; // 错误,不能将一个右值引用绑定到一个左值上
int &r2 = i*4; // 错误,i*4是右值
const int& r3 = i*4; // 正确,可以用一个const的引用来绑定一个右值
int&& rr2 = i*4; // 正确,将rr2绑定到一个右值上
int&& rr3 = rr2; // 错误,rr2是一个左值

76、什么是标准库的move函数?
move函数可以获取一个绑定到左值的右值引用,该函数定义在头文件utility中。

int&& r1 = 8;
int&& r2 = std::move(r1);

调用move意味着承诺,除了对该左值对象赋值或者销毁它以外,将不再使用它。也就是说我们可以销毁一个移后源对象,也可以对它赋值,但是不能使用一个移后源对象的值。

77、移动构造函数与移动赋值运算符

(1)移动构造函数

类似于拷贝构造函数,移动构造函数的第一个参数是该类型的一个引用,只不过是右值引用,其他任何默认参数都必须有默认参数。除了完成资源的移动,移动构造函数还必须确保移后源对象处于一个状态——销毁它是无害的。特别是,一旦资源完成移动,源对象必须不再指向被移动的资源——这些资源的所有权已经归属新创建的对象。

TestClass::TestClass(TestClass&& rhs) noexcept
: m_ptr(rhs.m_ptr) // 成员初始化器接管rhs中的资源
{
    // 令s进入这样的状态,对其运行析构函数是安全的
    rhs.m_ptr = nullprt;
}

(2)移动赋值运算符

与移动构造函数类似,但是最重要的一点是必须考虑自赋值的问题,因此我们不能一开始就释放左侧对象的资源,必须进行自赋值检查

TestClass& TestClass::operator=(TestClass&& rhs) noexcept
{
    // 先进行自赋值检查
    if(this != &rhs)
    {
        free(); // 释放已有的元素
        m_ptr = rhs.m_ptr; // 从rhs接管资源
        rhs.m_ptr = nullptr; // 将rhs置于可以析构的状态 
	}
    return *this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

czy1219

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值