C++常见面试题(六)

本博客内容:
一、左值和右值、右值引用
二、C++和C相比最大的特点:
三、构造函数可以是虚函数吗?
四、构造和析构可以调用虚函数吗?
五、C++11智能指针 (未深入理解)
六、C++的内存管理机制
七、C++调用C函数,为什么要加extern “C”?

一、左值和右值、右值引用

参考:https://blog.csdn.net/hyman_yx/article/details/52044632

1.左值和右值

C++11中所有值都是左值或右值。
可以取地址的,有变量名字的就是左值,反之为右值。
举例:int a=b+c;  a:有名字  &a可以得到它,就是左值
     b+c 是右值

2.右值、将亡值

C++98中右值就是纯右值,指临时变量值、不跟对象关联的字面量值
临时变量指的是:非引用返回的函数返回值、表达式等,如int func()返回值,表达式a+b;
不跟对象关联的字面量值,例如true,2,“c”等
C++11对右值进行了扩充。分为纯右值和将亡值。
将亡值是C++新增的跟右值引用相关的表达式,这些表达式通常是将要被移动的对象(移为他用),比如返回右值引用T && 的函数返回值,std::move的返回值,或者转换为T && 的类型转换函数的返回值。
将亡值可以理解为通过盗取其他变量内存空间的方式获取到的值。在确保其他变量不再被使用,或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。

3.左值引用、右值引用

都属于引用类型。都必须立即初始化。
常量左值引用是一个“万能”的引用类型。
可以接受4种。
int &a = 2;       # 左值引用绑定到右值,编译失败

int b = 2;        # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2;  # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2;  # 常量左值引用绑定到右值,编程通过

右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1=c;
int &&r2=std::move(c); //编译通过

这里写图片描述

二、C++和C相比最大的特点:

1)面向对象:封装,继承,多态。
2)引入引用代替指针。
3)const /inline/template替代宏常量。
4)namespace解决重名的问题。
5)STL提供高效的数据结构和算法

三、构造函数可以是虚函数吗?

每个对象的虚函数表指针都是在构造函数中初始化的,因为构造函数没执行完,所以虚函数表指针还没初始化好,构造函数的虚函数不起作用。

四、构造和析构可以调用虚函数吗?

就算调用也不起作用,就跟调用一般成员函数一样
不去作用,跟调用一般成员函数一样。析构函数的顺序是先派生类后基类,有可能内容已经被析构了,所以虚函数不起作用。

五、C++11智能指针 (未深入理解)

参考:https://www.cnblogs.com/wxquare/p/4759020.html

1.作用

C++11中引入,方便管理堆内存。使用普通指针,容易造成内存泄漏(忘记释放),二次释放,程序发生异常时内存泄漏等问题,使用智能指针能更好的管理堆内存。
理解的三个层次
1.浅层次看,利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,使得智能指针实质是一个对象,行为表现的却像一个指针。
2.智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch快忘记释放内存。多次释放也会造成程序崩溃。
3.智能指针还有一个作用是把值语义转换成为引用语义。

2.使用

头文件memory shared_ptr unique_ptr weak_ptr

2.1shared_ptr

多个指针指向相同的对象,使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用一次,内部的引用计数+1,析构一次,-1.减为0时,自动删除所指向的堆内存。内部的引用计数是线程安全的,但是对象的读取需要加锁。

1.初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。
也可以使用make_shared函数初始化。
不能将指针直接赋值给一个智能指针,一个是类,一个是指针。
2.拷贝和赋值:拷贝使得对象的引用计数加1,赋值使得原对象引用计数减1,计数为0,自动释放内存。后来指向的对象引用计数+1,指向后来的对象。
3.get函数获取原始指针。
4.注意不要用一个原始指针初始化多个shard_ptr,否则会造成二次释放同一个内存。
5.注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。

2.2unique_ptr

“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义,只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能够得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

2.3weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

六、C++的内存管理机制

http://blog.sina.com.cn/s/blog_8f52d13c0100vs6q.html
1.内存分配的形式有5部分,不再赘述
2.常见错误与对策

1.    内存分配未成功,却使用了它。
解决:使用内存之前检查指针是否为空
2.    内存分配虽然成功,但是尚未初始化就引用它
犯错原因:没有初始化的概念
        误以为内存的缺省初值全为0,导致引用初值错误。所以即便是赋初值也不可省略。
3. 内存分配成功并且已经初始化,但操作越过了内存的边界
4.忘记释放内存,造成内存泄漏  注意:释放后一定要将指针指为0或NULL。
5.释放了内存却继续使用它。

3.良好的规则:
1.申请后,检查指针是否为NULL。
2.不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值引用。
3.避免数组或指针的下标越界。
4.动态内存的申请与释放必须配对,防止内存泄漏
5.用free和delete释放了内存之后,立即将指针设置为NULL。防止产生野指针。

七、C++调用C函数,为什么要加extern “C”?

参考:https://blog.csdn.net/u011046042/article/details/49382431
C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”)。C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。
比如 void fun(int x,char y);
C++有重载功能,编译后的函数名变为__fun_int_char
而C语言没有重载,编译生成的函数名为__fun
要是没有加extern “c”,C++连接器链接查找fun函数是就会按照__fun_int_char这个名字查找,自然会提示连接失败,找不到。而加上externa “c”,就是告诉链接器要按照C语言规则去查找,这时链接器就会按照__fun查找,这样才会链接成功了

八、写一个数组类

#include <iostream>
#include<vector>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;
class MyVect
{
public:
    MyVect();
    MyVect(int n);
    MyVect(const MyVect & other);
    MyVect & operator=(const MyVect & rhs); 
    void push_back(double var);
    double pop_back();
    void insert(int n,double var);
    void erase(int n);
    void reallocate();
    void clear();
    void print();
    double & operator[](int n);
    const double & operator[](int n) const;

    virtual ~MyVect();
private:
    double * data; //数组指针
    int data_size;   //数组实际大小
    int data_capacity; //数组容量
};
MyVect::MyVect()
{
    data=NULL;
    data_size=0;
    data_capacity=0;
}
MyVect::MyVect(int n)
{
    data_size=n;
    data_capacity=2*n+1;
    data=new double [data_capacity];
}
double & MyVect::operator[] (int n)
{
    return data[n];
}
const double & MyVect::operator[](int n) const
{
    return data[n];
}
MyVect & MyVect::operator=(const MyVect & rhs)
{
    if(this==&rhs)
        return *this;
    data_capacity=rhs.data_capacity;
    data_size=rhs.data_size;
    if(data!=NULL)
    {
        delete [ ] data;
    }
    data=new double [ data_capacity];
    for(int i=0;i<rhs.data_size;i++)
        data[i]=rhs.data[i];
    return *this;
}
MyVect::MyVect(const MyVect & other)
{
    this->data_size=other.data_size;
    this->data_capacity=other.data_capacity;
    data=new double [data_capacity];
    for(int i=0;i<data_size;i++)
        data[i]=other.data[i];
}
void MyVect::reallocate()
{
    if(data_size==data_capacity)
    {
        double * old=data;
        data_capacity=2*data_size+1;
        data=new double [data_capacity];
        for(int i=0;i<data_size;i++)
            data[i]=old[i];
        if(old!=NULL)
            delete [ ] old;
    }
}
void MyVect::push_back(double var)
{
    this->reallocate();
    data[data_size]=var;
    data_size++;
}
double MyVect::pop_back()
{
    double out=data[data_size-1];
    data_size--;
    return out;
}
void MyVect::insert(int n ,double var)
{
    reallocate();
    for (int i = data_size; i >n; i--)
    {
        data[i] = data[i-1];
    }
    data[n] = var;
    data_size++;
}

void MyVect::erase(int n)
{
    for (int i = n; i < data_size; i++)
    {
        data[i] = data[i + 1];
    }
    data_size--;
}

void MyVect::clear()
{
    data_size = 0;
    data_capacity = 0;
    if (data != nullptr)
        delete [] data;
    data = nullptr; //此句不可忘,否则析构时删除野指针出现内存错误
}

void MyVect::print()
{
    for (int i = 0; i < data_size; i++)
        cout << data[i] << '\t';
    cout << endl;
}
MyVect::~MyVect()
{
    data_size=0;
    data_capacity=0;
    if(data!=nullptr)
    {
        delete [] data;
    }
    data=NULL;
}

猜你喜欢

转载自blog.csdn.net/xiongluo0628/article/details/82320211