C++笔试记录 2021年9月16日

53 篇文章 1 订阅
4 篇文章 1 订阅

1,函数模板缺省情况下都是内联的   需要进一步的学习

#include <iostream>
using namespace std;

class Base
{
public:
    ~Base()
    {
        cout<<"~Base()"<<endl;
    }

};



class ABase : public Base
{
public:
    virtual ~ABase()
    {
        cout<<"~ABase()"<<endl;
    }

};


int main(){
    Base *pBase = new ABase;
    delete pBase;//此处出错
    getchar();
    return 0;

}

assert断言 

  • assert断言是用来检查非法情况而不是错误情况的,用来帮开发者快速定位问题的位置。如果表达式为假(0),那么首先向错误流strerr打印一条错误信息,然后通过abort函数终止程序的运行。NDEBUG宏打开状态时assert宏是可用的。默认情况下,assert宏只有在Debug版本才起作用,而在Release版本中将被忽略。但在许多操作系统的C程序中,Release版本中也将NDEBUG宏依然为打开状态。  断言不是用于处理错误情况的
  • 断言assert函数,C语言assert函数完全攻略
  • C/C++ 学习笔记八(断言与异常处理) - 云+社区 - 腾讯云

类的析构函数可以是虚函数 但是构造函数不可以

  • 调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数
  • 纯虚拟函数是用来提供函数接口的,虚拟函数是用来提供函数接口和默认的函数操作,非虚拟函数是用来提供函数操作的。
  • 构造函数,析构函数可不可以是虚函数?_学海无涯-CSDN博客

类中有指向其他资源的指针,且这个资源由类本身进行释放,那么可以使用编译系统生成的默认构造函数进行对象的复制

子类可以访问父类的保护成员,子类友元类可以通过子类对象去访问父类的保护成员 

#include <iostream>

class father{
protected:
    int father_protected = 123;
public:
    int father_public = 456;
};

class son : public father{
public:
    void print_protected(){
        std::cout << father_protected << std::endl;
    }
    void print_public(){
        std::cout << father_public << std::endl;
    }
    int wed = 123;
    friend class son_friend;
};

class son_friend{
public:
    void friend_print(son son1){
//        std::cout << son1.wed << std::endl;
//        son1.print_public();
        son1.print_protected();
    }
};
int main(){
    son son1;
    son1.wed = 456789;
    son_friend ofo;
    ofo.friend_print(son1);
}

两个指针p和q,指向同一个数组,则他们可以进行关系运算,例如p<q,p!=q,指针可以做减法操作,指针可以同整数相加减  正确

    int a[10]={0,1,2,3,4,5,6,7,8,9};
    int *p = a;
    int *q = &a[9];
    std::cout << "p和q之间的距离是:" << q-p << std::endl;

    std::cout << "在p的基础上移动 8个距离后的数值为:" << *(p+8) << std::endl;

判断以下定义是否正确  错误

  • 使用const进行修饰 因此在函数的内部 不可以对元素进行修改,去掉const 编译通过
class Date{
private:
    int d,m,y;
public:
    int year() const {
        return y++;
    };
};

const 成员函数

  •   如果不使用const修饰符 就会造成对于函数的返回数组进行修改
class A{
private:
    int data;
public:
    A(int num):data(num){};
    ~A(){};
    int& get_data(){
        return data;
    }
};

int main(){
    A a(1);
    a.get_data() = 4;
    std::cout << a.get_data() << std::endl;
}
  • 使用const进行限定  阻止对于参数的修改
class A{
private:
    int data;
public:
    A(int num):data(num){};
    ~A(){};
    const int& get_data(){
        return data;
    }
};

int main(){
    A a(1);
//    a.get_data() = 4;// 不允许
    std::cout << a.get_data() << std::endl;
}
class A{
private:
    int data;
public:
    A(int num):data(num){};
    ~A(){};
    int& get_data(){
        return data;
    }
};

int main(){
    const A a(1);
//    a.get_data() = 4;// 不允许
    get_data(a);//错误
    std::cout << a.get_data() << std::endl;
}
  •  a 是一个const 对象, 它调用了 get_data 方法,所以函数签名应该是: get_data(a){} 而 a是一个 const 对象, 我们默认的 this 指针并不是 const 类型,所以参数类型不匹配, 编译无法通过! 这下我们终于清楚了, const 修饰成员函数, 根本上是修饰了 this 指针。
  • 补充一点,如果成员函数同时具有 const 和 non-const 两个版本的话, const 对象只能调用const成员函数, non-const 对象只能调用 non-const 成员函数。

子类没有定义构造函数,则调用父类的无参数的构造方法,否则不调用父类无参构造函数

  • 在C++中子类继承和调用父类的构造函数方法_菜头-CSDN博客_c++子类构造函数调用父类构造函数
  • 子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法,因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法
  • 如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建
  • 如果子类没有定义构造方法,则调用父类的无参数的构造方法
  • 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法
  • 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数
  • 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数
  • 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)
  • 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
// 子类无构造方法 则调用父类的无参数的构造方法
class father_1{
public:
    father_1(){
        std::cout << "father 1 create!\n";
    }
};

class children_1 : public father_1{

};
int main(){
    children_1 children1;
}
//子类定义了构造函数 则先调用父类的构造函数 再次调用子类的构造函数
class father_1{
public:
    father_1(){
        std::cout << "father 1 create!\n";
    }
};

class children_1 : public father_1{
public:
    children_1(){
        std::cout << "children_1 create!\n";
    }
};
int main(){
    children_1 *child = new children_1();

}

虚函数是可以内联的吗?可以减少函数调用的开销,提高效率

  • C++面试题:虚函数(virtual)可以是内联函数(inline)吗?_changyi9995的博客-CSDN博客
  • 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联
  • 内联是在发生在编译期间,编译器会自主选择内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。 inline virtual唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
class Base{
public:
    inline virtual void who(){
        std::cout << "I am Base\n";
    }
    virtual ~Base();
};

class Derived : public Base{
public:
    inline void who(){
        std::cout << "I am Derived\n";
    }
};
int main(){
    // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,
    // 编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
    Base b;
    b.who();

    // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,
    // 所以不能为内联。
    Base *ptr = new Derived();
    ptr->who();
    // 因为Base有虚析构函数(virtual ~Base() {}),
    //所以 delete 时,会先调用派生类(Derived)析构函数,
    //再调用基类(Base)析构函数,防止内存泄漏。
    delete ptr;
    ptr = nullptr;

}
  • 链接给的例子 将析构函数定义为虚函数,但是会出错,除了删除的方式以外,还可以将其定义为default的方式
  •  virtual ~Base() = default;
class Base{
public:
    inline virtual void who(){
        std::cout << "I am Base\n";
    }
    virtual ~Base() = default;
};

class Derived : public Base{
public:
    inline void who(){
        std::cout << "I am Derived\n";
    }
};
int main(){
    // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,
    // 编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
    Base b;
    b.who();

    // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,
    // 所以不能为内联。
    Base *ptr = new Derived();
    ptr->who();
    // 因为Base有虚析构函数(virtual ~Base() {}),
    //所以 delete 时,会先调用派生类(Derived)析构函数,
    //再调用基类(Base)析构函数,防止内存泄漏。
    delete ptr;
    ptr = nullptr;

}

C++命名空间必须要指定名字 错误 可以

  • C++/C++11中命名空间(namespace)的使用_网络资源是无限的-CSDN博客_c++ namespace
  • 未命名的命名空间(unnamed namespace):是指关键字namespace后紧跟花括号括起来的一系列声明语句。未命名的命名空间中定义的变量拥有静态生命周期:它们在第一次使用前创建,并且直到程序结束才销毁。
  •  一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件。每个文件定义自己的未命名的命名空间,如果两个文件都含有未命名的命名空间,则这两个空间互相无关。如果一个头文件定义了未命名的命名空间,则该命名空间中定义的名字将在每个包含了该头文件的文件中对应不同实体。和其它命名空间不同,未命名的命名空间仅在特定的文件内有效,其作用范围不会横跨多个不同的文件。

一个类可以有多个构造函数 但是只能有一个析构函数

二叉树的前序、中序后续遍历 常常使用递归的方式来实现

友元函数破坏了类的封装性

preview

preview

preview

#include机制用于将源程序片段收集在一起,形成一个完整的编译单元作为编译器的输入。

关于纯虚函数 说法正确的是

  • 具有纯虚函数的类称为虚基类 错误 虚基类是指虚继承的基类,主要解决的是从不同路径多次继承同一个基类的问题,与纯虚函数无关
  • 具有纯虚函数的类不能创建类对象  正确
  • 一个基类说明有纯虚函数,其派生类一定要实现该纯虚函数  错误 可以不实现纯虚函数,那么子类也将作为 纯虚类
  • 纯虚函数是一种特殊的虚函数 他是个空函数  错误 ,空函数是指不执行任何语句直接返回的函数,显然纯虚函数不满足
  • 纯虚函数是一种特殊的虚函数,它是个空函数_百度知道

选择正确的语句是: 

int main(){
    char* pBuf = new char(20);
    sprintf(pBuf,"%s","This is a test.\n");
    printf("%s",pBuf);
    delete pBuf;
};

编译过程中处理#include的语句的阶段是  

  • 预编译  感觉是预编译阶段,但是没有找到明确的答案
  • 语法分析
  • 词法分析
  • 二进制代码

错误的赋值表达式


int mian(){
    int a[10];
    int*p = nullptr;
    //赋值表达式的位置
    p = &a;//错误,因为a就代表了地址 即 a的含义等效于a[0]
    p = &a[0];
    *p = 1;
    p = a;
    
}

  设置虚基类的目的是为了

多重继承定义:

  • 一个派生类(D)有2个或2个以上的基类(B和C);
  • 多重继承引起的二义性:
  • 假如上述这些基类(B和C)具有相同的基类A,A中的成员数据和成员函数,最终都会以双份的形式拷贝到类D中,
  • 那么调用的时候就会出现二义性问题。

虚基类:

  • 专门用来解决多重继承引起的二义性问题;(可以理解为D直接从A继承)
  • 虚基类的具体实现的注意细节有很多,这里不再列举了,我认为只需要了解原理即可。

下列常量定义正确的是

    const int VERSION = 200;
    const int g_xx[4] = {1,2,4,8};
    const char* const hh = "Hello world";
不确定

    enum Color{BLUE,RED};
    const enum Color gg =BLUE;
    const enum Color gg =BLUE;
//    gg = RED; //错误 被const修饰,不可以更改
    const char* const hh = "Hello world";

 关于命名空间的理解正确的是  搜查 

  • 通过使用声明引入的元素名字会遮蔽名字相同的非局部声明
  • 表达式::a 一般表示引用全局作用域中声明的元素a
  • 使用关键字namespace定义的命名空间必须要有显示的名字必须是独一无二的 错误,可以不需要显示的名字
  • 名字空间是可以嵌套的  ✅

继承 和访问之间的关系

程序执行后会有错误或者什么效果?

#include <iostream>
#include <vector>

#define MAX 255
int main(){
//    unsigned char A[MAX];
//    for (int i = 0; i <= MAX+8; ++i) {
//        A[i] = 'c';
//    }

    unsigned char j = 'a';
    std::vector<char>temp(MAX,'\0');
    for (int i = 0; i <= MAX; ++i) {
        temp.at(i) = j;
    }
    for (int j = 0; j <= MAX; ++j) {
        std::cout << temp[j] << " ";
    }
}

关于动态库和静态库的说法 正确的是

  • 程序运行的时候不需要静态库 正确
  • 静态库在程序编译的时候会被连接到目标代码中 错误,静态库编译的时候会被直接拷贝,动态库是被链接
  • 动态库在程序运行时才被载入  正确
  • 动态库 在程序编译的时候不会被连接到目标代码  错误

补充

  • 库是一段编译好的二进制的代码,加上头文件供别人进行调用
  • 使用库 的场景:1,不希望别人看到源码,以库的方式进行封装,只需要暴露头文件;减少编译的时间,因为库是已经编译好的二进制的代码,编译的时候只需要简单的的link,不需要浪费编译的时间

静态库

  • windows下是lib,linux下是.a,静态库在编译的时候会被直接拷贝一份,复制到目标代码里面,这段代码在目标程序里面就不会被改变了;静态库的好处是 编译完成之后库文件就不会有作用了,目标程序没有外部依赖,直接就可以运行,缺点就是使得目标体积变大

动态库

  • 动态链接库,win下是dll,linux下是so,mac下是dylib。动态库在编译的时候不会被拷贝到目标程序中,目标程序只会存储动态库的引用。只有程序运行的时候才会被加载;动态库不需要拷贝到目标程序中,不会影响目标程序的体积,同一份库文件可以被多个程序共用。编译才会被载入的特性,因此随时可以对其进行替换。但是 动态加载会带来性能的损耗,使用动态库很依赖外部的环境,缺库会导致程序无法运行。

参考链接

const 和 宏 的区别

  • 宏缺少类型检查,const具备严格的类型审查  正确
  • 宏在编译链接和运行的时候没有符号,const常量在编译和调试的时候(打开编译器调试开关)可见
  • 宏定义在函数的内部,函数作用域结束后仍然可用,const常量只在作用域内可见
  • 宏在编译阶段展开  const常量在编译阶段处理  错误;宏在预处理阶段,const变量在编译阶段进行处理

 C++类不占据内存 只有实例化对象之后才会占据内存

C++语言中提供了哪些代码重用的方式()

  • 继承
  • 多态
  • 模板

关于运算符重载,下列说法不正确的是

  • 重载时, 运算符的操作个数可以改变  错误
  • 重载时,操作符的优先级可以改变  错误
  • 重载时,运算符号的功能可以改变  正确
  • 重载时,运算符号的结合性可以改变  错误

注意事项

  • 不是所有的运算符号都可以被重载
  • 重载运算符号 不可以改变其优先级和结合性
  • 重载不会改变运算符的用法,原有的操作数、操作数在左边还是在右边都不会被改变
  • 运算符重载不能有默认的参数
  • 运算符重载既可以作为类的成员函数,也可以作为全局函数
  • 箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载
  • 有关运算符重载正确的描述是()。  __牛客网

虚函数说法正确的是

  • 成员函数被声明为虚函数之后,其派生类的同名函数都自动转换成 虚函数  正确
  • 虚函数不可以有函数体  错误  纯虚函数在函数可以拥有函数体,但是类中只可以写纯虚函数的声明,纯虚函数的定义必须写在类外
  • 通常,如果一个类可以被继承,构造函数和析构函数都有必要被定义为虚函数。错误,析构函数一般可以被定义成虚函数,但是构造函数是绝对不可以被定义为虚函数
  • 虚函数在基类中可不实现,但是派生类中必须实现  错误 派生类也可以不实现
  • 我难道不配拥有函数体吗?---【纯虚函数函数体】_Alice_lihao的博客-CSDN博客
1. 虚函数是可以[New一个对象的时候要根据虚函数的函数体来填虚表;而内联函数没有函数体,只是在预编译阶段展开]内联的,这样就可以减少函数调用的开销,提高效率(错误) 2. 一个类里可以同时存在[同一个类里无论什么函数都不能函数名和参数完全一样]参数和函数名都相同的虚函数与静态函数(错误) 3. 父类的析构函数是非虚的,但是子类的析构函数是虚的,delete子类指针(指向该子类对象)[特殊情况,参见题5],会调用父类的析构函数(正确)//任何情况下删除子类都会调用到父类的析构函数 4.对于下面的类CA,sizeof(CA) = _B_: A. 4 B. 8 C. 12 D. 16 class CA { public: CA(); virtual ~CA(); //因为有虚函数,所以会有4个字节的虚表指针 private: int m_iTime; //成员变量4个字节 public: int GetTime(); int SetTime(int iTime); }; 5.下面这段程序,打印结果是_A_: A. 1 B. 2 C. 3 D. 以上都不对 int g_iCount = 0; class CParent { public: CParent() {} ~CParent() {g_iCount += 1;} }; class CSon : public CParent { public: CSon() {} ~CSon() {g_iCount += 2;} }; main() { CParent* p = new CSon(); delete p[由于p被声明成父类指针,并且父类和子类的析构函数都非虚,因此delete操作只能根据p指针声明的类型来调用父类的析构函数]; std::cout << g_iCount << std::endl; } 6.请问下面这段程序的输出结果是_A_: A. 2,1, B. 2,2, C. 1,1, D. 1,2, class CParent { public: CParent() {} virtual ~CParent() {} public: virtual void Print() { std::cout << "1,"; }; }; class CSon : public CParent { public: CSon() {}; virtual ~CSon() {}; public: void Print() { std::cout << "2,"; }; }; void Test1(CParent& oParent[这里是引用了一个外部对象,该对象的虚表不会发生变化]) {oParent.Print();} void Test2(CParent oParent[这里会在栈空间内重新构造一个CParent类的对象,如果传入实参的类型与CParent不同则虚表会发生变化]) {oParent.Print();} main() { CSon * p = new CSon(); Test1(*p); //这里只是一个引用 Test2(*p); //这里会在栈空间重新构造Cparent类对象 delete p; } 7.请问下面这段程序的输出结果是_D_: A. 2,1, B. 2,2, C. 1,1, D. 1,2, class CParent { public: CParent() {} virtual ~CParent() {} public: void Print(){ std::cout << "1," ; }; }; class CSon : public CParent { public: CSon() {} virtual ~CSon() {} public: void Print(){ std::cout << "2,"; }; }; main() { CSon oSon; CParent * pParent = &oSon; CSon * pSon = &oSon; pParent->Print(); pSon->Print();[由于父类和子类的Print函数都非虚,所以根据指针类型决定调用关系] } 8.请问下面这段程序的输出结果是_C_: A. 2,1, B. 2,2, C. 1,2, D. 1,1, class CParent { public: CParent() {Print();} virtual ~CParent() {} public: virtual void Print(){ std::cout << "1,"; } }; class CSon : public CParent { public: CSon() {Print();} virtual ~CSon() {} public: void Print(){ std::cout << "2,"; } }; main() { CParent * pParent = new CSon()[类的构造过程遵循压栈原则,构造过程虚表尚未建立成功,是静态调用虚函数]; delete pParent; } 9.请问下面这段程序的输出结果是_D_: A. 2,2, B. 2, C. 输出结果不确定 D. 以上都不对 class CParent { public: CParent() {Print();[构造子类对象时调用到父类的构造函数,但父类的Print函数是纯虚的,没有实现,所以这里的调用不成功,编译会出错]} virtual ~CParent() {} public: virtual void Print() = 0; }; class CSon : public CParent { public: CSon() {Print();} virtual ~CSon() {} public: void Print() { std::cout << "2,"; }; }; main() { CParent * pParent = new CSon(); delete pParent; } 10.请仔细阅读以下程序: class Base { public: virtual bool operator == (int iValue) { std::cout << "I am Base class !" << std::endl; return true; } virtual ~Base(){} }; class Derive: public Base { public: virtual bool operator == (int iValue) { std::cout << "I am Derive class !" << std::endl; return true; } virtual ~Derive(){} }; int main() { Derive derive; Base* pBase = &derive; Derive* pDerive = &derive; *pBase == 0; *pDerive == 0;[重载操作符声明为virtual使操作符产生多态性] return 0; } 程序的输出结果是_B_: A、I am Base class ! I am base class ! B、I am Derive class ! I am Derive class ! C、I am base class ! I am Derive class ! D、I am Derive class ! I am Base class !
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值