C/C++基本概念

参考:

http://blog.csdn.net/shihui512/article/details/9092439

https://www.cnblogs.com/lca1826/p/6502130.html

https://www.1024do.com/?p=2042


1. new/delete和malloc/free的关系

malloc和free都是C/C++语言的标准库函数,new/delete是C++的运算符。

new调用构造函数,delete会调用对象的析构函数,而free只会释放内存。

它们都可用于申请动态内存和释放内存。但对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加给malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意:new/delete不是库函数。

2. delete与delete[]的区别

delete只会调用一次析构函数,而delete[]会调用每一个成员函数的析构函数。

在More Effective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。”delete与new配套,delete []与new []配套。

对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

3. 子类构造和析构顺序

定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

4. 多态、虚函数、纯虚函数、抽象类

多态:对于不同对象接收相同消息产生不同的动作。C++分为运行时多态和编译时多态,运行时多态通过继承和虚函数体现;编译时多态通过函数或运算符重载体现。

虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

纯虚函数:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在的纯虚函数不具备函数的功能,一般不能直接被调用。

抽象类:从基类继承来的纯虚函数,在派生类中仍是虚函数,如果一个类至少有一个纯虚函数,那么这个类被称为抽象类。

注意:定义一个函数为虚函数,不代表函数为不被实现的函数。而是为了允许用基类的指针来调用子类的这个函数。定义一个纯虚函数,才代表函数没有被实现。

5. 引用

引用就是某个目标变量的“别名”,对应用的操作与变量直接操作效果完全相同。声明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因为该引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

6. 引用作为函数参数的好处

传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对相应的目标对象(在主调函数)的操作。

使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

7. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别

从定义上来看:

重载:是指允许存在多个同名函数,而这些函数的参数列表不同,包括参数个数和参数类型不同,也可以都不同。

重写:指子类重新定义父类虚函数的方法。

从实现原理看:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定)。

重写:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

8. 分别写出bool,int,float,指针类型的变量a 与“零”的比较语句

bool:if(a) or if(!a)

int:if(a == 0)

float:if(a < 0.000001 && a > 0.000001)

pointer:if(a == NULL) or if(a != NULL)

9. const相比#define的优点

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

10. 数组和指针的区别

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

11. 引用和指针的区别

引用必须被初始化,指针不必。

引用初始化以后不能被改变,指针可以改变所指的对象。

不存在指向空值的引用,但是存在指向空值的指针。

12. 基类的析构函数不是虚函数,会造成什么后果

派生类的析构函数用不上,会造成资源的泄漏。

13.全局变量和局部变量有什么区别

生命周期不同:全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;

使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。

内存分配位置不同:全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

14. 写出完整版的strcpy函数(10分)

2分:

void strcpy( char *strDest, char *strSrc )
{
    while( (*strDest++ = * strSrc++) != '\0' );
}

4分:

void strcpy( char *strDest, const char *strSrc )
//将源字符串加const,表明其为输入参数,加2分
{
    while( (*strDest++ = * strSrc++) != '\0' );
}

7分:

void strcpy(char *strDest, const char *strSrc)
{
    //对源地址和目的地址加非0断言,加3分
    assert( (strDest != NULL) && (strSrc != NULL) );
    while( (*strDest++ = * strSrc++) != '\0' );
}

10分:

char * strcpy( char *strDest, const char *strSrc )
{
    assert( (strDest != NULL) && (strSrc != NULL) );
    char *address = strDest;
    while( (*strDest++ = * strSrc++) != '\0' );
    return address;
}

15. extern "C"的作用

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。

函数被C++编译后在symbol库中的名字与C语言的不同。

void foo(int x, int y);

该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。   

为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

16.String类

class String
{
public:
    String(const char *str = NULL); // 普通构造函数
    String(const String &other); // 拷贝构造函数
    ~ String(void); // 析构函数
    String & operator =(const String &other); // 赋值函数
private:
    char *m_data; // 用于保存字符串
};
//普通构造函数
String::String(const char *str)
{
    if(str==NULL)
    {
        m_data = new char[1];   // 得分点:对空字符串自动申请存放结束标志'\0'的空
        *m_data = '\0';         //加分点:对m_data加NULL 判断
    }
    else
    {
        int length = strlen(str);
        m_data = new char[length+1];    // 若能加 NULL 判断则更好
        strcpy(m_data, str);
    }
}
// String的析构函数
String::~String(void)
{
    delete [] m_data;
}
//拷贝构造函数
String::String(const String &other) // 得分点:输入参数为const型
{
    int length = strlen(other.m_data);
    m_data = new char[length+1];    //加分点:对m_data加NULL 判断
    strcpy(m_data, other.m_data);
}
//赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{
    if(this == &other)  //得分点:检查自赋值
        return *this;
    delete [] m_data;   //得分点:释放原有的内存资源
    int length = strlen( other.m_data );
    m_data = new char[length+1];    //加分点:对m_data加NULL 判断
    strcpy( m_data, other.m_data );
    return *this;   //得分点:返回本对象的引用
}

17.static和const关键字

static:

(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。  

const:

(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量;

(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

18.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

int checkCPU()
{
    {
        union w
        {
            int a;
            char b;
        } c;
        c.a = 1;
        return (c.b == 1);
    }
}

19.构造函数为什么不能定义为虚函数?

构造函数的调用时为了创建对象,而虚函数的使用。会将对象的前四个字节用来存放虚表指针,构造函数都未创建对象,也就当然不能定义为虚函数了。

20.静态成员函数为什么不能定义为虚函数?

虚函数是与类对象捆绑的。而类的普通成员函数包括虚函数在编译时加入this指针,通过这种方式可以与对象捆绑,而静态函数编译时不加this,因为静态函数是给所有类对象公用的,所以在编译时没有加入this指针,所以无法与对象捆绑,而希函数就是靠着与对象捆绑加上虚函数列表才实现了动态捆绑。所以没有this指针虚函数就无从谈起。

21.赋值运算符“=”是否可以是虚函数?

可以。受限赋值运算符定义成虚函数的目的就是实现多态机制,但是根据赋值兼容该规则可以知道派生类的对象可以直接赋值给基类对象,而基类对象不能直接赋值给派生类对象。鉴于这种情况,为了防止父类给子类赋值导致程序崩溃,因此建议不要将赋值运算符重载定义为虚函数。

22.友元函数为什么不能定义为虚函数?

友元函数并非类的成员函数,也就无从谈起定义为虚函数了。

23.析构函数能否定义为虚函数?

可以,而且建议析构函数一般都定义为虚函数。如果基类析构函数不是虚函数,用基类指针去调用派生类的成员,在释放指针的过程中,就仅仅会释放基类的资源,而没有调用派生类的析构函数。这种情况下只能删除基类的对象,而不能删除派生类的对象,造成内存泄漏。

24.继承体系中同名函数的关系?

重载:在同一作用域;函数名相同,参数不同;返回值可以不同

重写(覆盖):不再同一作用域(分别在基类和派生类);函数名相同,参数相同,返回值相同(协变除外);基类函数必须有virtual关键字;访问修饰符可以不同

重定义(隐藏):在不同作用域(分别在基类和派生类);函数名相同;在基类和派生类中只要不构成重写就是重定义

猜你喜欢

转载自blog.csdn.net/qq_20283969/article/details/82011809