C++常见笔试题(求职)

  1. int f() 与 int f(void) 有区别吗? 如果有区别是什么?

    在c语言中:
    int f() 表示返回值为int,接受任意参数的函数
    f(void)表示返回值为int的无参函数
    在c++int f()和 int f(void)具有相同的意义
    表示返回值为int的无参函数
    
  2. const

    C语言中的const - 不能定义真正意义上的常量
    1. 修饰的变量是只读的,本质还是变量
    2. 修饰的局部变量在栈上分配空间
    3. 修饰的全局变量在只读存储区分配空间
    4. 只在编译期有用,在运行期无用
    const 修饰的变量不是真的常量,他只是告诉编译器该变量不能出现在复制符号的左边
    
    c++ 中的const - 类似于宏定义
    当碰见const声明时在符号表中放入常量
    编译过程中若发现使用常量则直接以符号表中的值替换
    编译过程中若发现下述情况则给对应的常量分配存储空间
    	对const常量使用extern
    	对const常量使用&操作符
    注意:c++编译器虽然可能为const常量分配空间,但不会使用器存储空间的值
        
    const int i = 5;
    int *p = (int *)&i;
    printf("i = %d\n", i);
    printf("*p = %d\n",*p);
    
    const与宏定义的不同
    -const常量由编译器处理
    -编译器对const常量进行类型检查和作用域检查
    -宏定义由预处理器处理,单纯的文本替换
    
    const 什么时候是只读变量?什么时候是常量?
    const常量的判别标准
    - 只有用字面量初始化的const常量才会进入符号表
    - 使用其他变量初始化的 const常量仍然是只读变量
    - 被volatile 修饰的const常量不会进入符号表
    - 在编译期间不能直接赋初始值的const标识符,都会被作为只读变量处理
    
  3. c++ 中的三目运算符

    int a = 1;
    int b = 2;
    (a<b ? a : b) = 3;
    (a<b ? 1 : b) = 3;  //erro
    
    c语言中的三目运算符返回值是变量值不能作为左值使用
    c++的三目运算符可直接返回变量本身,既可作为右值使用,又可作为左值使用
    注意:三目运算符可能返回的之中如果有一个是常量值,则不能作为左值使用
    
  4. c++中的引用

    定义和变量名的本质
    - 定义一个变量的过程是对一段内存空间的抽象
    - 变量是一段实际连续存储空间的别名
    - 程序中通过变量来申请并命名存储空间
    - 通过变量的名字可以使用存储空间
    
    一段连续的存储空间只能由一个别名吗?
    c++的引用
    - 引用可以看作一个一定义变量的别名
    - 引用的语法 Type& name = var;
    注意:普通应用在定义时必须用同类型的便令进行初始化
    
    引用的意义:
    - 引用作为变量别名而存在,因此在一些场合可以代替指针
    - 引用相对于指针来说具有更好的可读性和实用性
    注意:函数中的引用形参不需要进行初始化
    void swap (int& a, int& b)
    {
    	int t = a;
    	a = b;
    	b = t;
    }
    
    void swap(int* a, int* b)
    {
    	int t = *a;
    	*a = *b;
    	*b = t;
    }
    
    - 功能性:可以满足多数需要使用指针的场合
    - 安全性:可以避开由于指针操作不当而带来的内存错误
    - 操作性:简单易用,又不失功能强大
    
    特殊的引用 - const引用
    - 在c++中可以声明const引用
    - const Type& name = var;
    - const引用让变量拥有只读属性
    
    int a = 4;
    const int& b = a;
    int* p = (int*)&b;
    b = 5;    //error
    *p = 5;   //o的k
    
    引用的本质
    - 引用在c++ 中的内部实现是一个指针常量
    Type& name; <==> Type* const name;
    注意:
    1.c++编译器在编译过程中用指针常量作为引用的内部实现,因此引用所占用的空间大小与指针相同。
    2.从实用的角度,应用知识一个别名,c++为了实用性而隐藏了引用的存储空间这一细节
    
    
    
    引用和指针
    指针是一个变量
    - 值为一个内存地址,不需要初始化,可以保存不同的地址
    - 通过指针可以访问对应内存地址中的值
    - 指针可以被const修饰成为常量或者只读变量
    引用只是一个变量的新名字
    - 对引用的操作(赋值、取地址等)都会传递到代表的变量上
    - const 引用使其代表的变量具有只读属性
    - 引用必须在定义是初始化,之后无法代表其它变量
        
    从使用c++的角度看
    - 引用与指针没有任何关系
    - 引用是变量的新名字,操作引用就是操作对应的变量
    从c++ 编译器的角度来看
    - 为了支持新概念"引用"必须要有一个有效的解决方案,也就是使用指针常量来实现
    - 因此 "引用" 在定义时必须初始化
    
    
  5. 内联函数

    c++中推荐使用内联函数替代宏代码片段
    c++中使用inline关键字声明内联函数
    inline int func(int a, int b)
    {
    	return a<b? a:b;
    }
    注意:内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求
    
    
    c++编译器可以将一个函数进行内联编译
    被c++编译器内联编译的函数叫做内联函数
    c++编译器直接将函数体插入函数调用的地方
    内联函数没有普通函数调用时的额外开销(压栈、跳转、返回)
    c++编译器不一定满足函数的内联请求
    
    
    内联函数与宏函数的区别
    内联函数具有普通函数的特征(参数、返回类型检查等)
    宏定义代码片段由预处理器处理,进行简单的字符替换,没有任何编译过程,因此可能出现副作用
    
    
  6. 函数的重载

    重载 - 同一个标识符在不同的上下文有不同的意义
    
    
    函数的重载
    - 用同一个函数名定义不同的函数
    - 但函数名和不同的参数搭配时函数的含义不同
       
    int func(int x)
    {
        return x;
    }
    int func(int a, int b)
    {
        return a+b;
    }
    int func(const char *s)
    {
        return strlen(s);
    }
    函数重载至少满足一个条件:
    - 参数个数不同
    - 参数类型不同
    - 参数顺序不同
    
    
    编译器调用重载函数的准则
    - 将所有同名函数作为候选者
    - 尝试寻找可行的候选函数
    	精确匹配实参
    	通过默认参数能够匹配实参
    	通过默认类型转换匹配实参
    - 匹配失败
    	最终寻找到的候选函数不唯一,则出现二义性,编译失败
    	无法匹配所有候选者,函数未定义,编译失败
    
    
    函数重载的注意事项 - 函数重载是由函数名和参数列表决定的
    - 重载函数在本质上是相互独立的不同函数
    - 重载函数的函数类型不同
    - 函数返回值不能作为函数重载的依据
    
    
  7. c++ 和 c相互调用

    - c++ 编译器会优先使用c++编译的方式
    - extern 关键字能强制让c++编译器仅从c方式的编译
        exter "C"
    	{
        	//do C-style cpmpilation here
    	}
    
    
  8. 怎样判断一段代码是由g++编译器编译还是gcc编译器编译

    __cplusplus 是 g++ 编译器内置的标准宏定义,当使用g++编译器编译源码的时候此宏会被定义
    
    
  9. c++中的动态内存分配

    c++ 中的动态内存分配
    - 通过new关键字进行动态内存申请,此动态内存申请是基于类型进行的
    - delete关键字用于内存释放
    
    
    new关键字 与 malloc函数的区别
    new关键字
    - 是c++的一部分
    - 以具体类型为单位进行内存分配
    - 在申请单个类型变量时刻进行初始化
    malloc
    - 由c库提供的函数
    - 以字节为单位进行内存分配
    - 不具备内存初始化的特性
    
    
  10. 在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

    	首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
    
    通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern "C"是连接申明(linkage declaration),extern
    "C"修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y);该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
    所以,可以用一句话概括extern “C”这个声明的真实目的:解决名字匹配问题,实现C++与C的混合编程。
    
    
  11. 评价一下C/C++各自的特点

    C语言是一种结构化语言,面向过程,基于算法和数据结构,所考虑的是如何通过一个过程或者函数从输入得到输出。
    
    C++是面向对象,基于类、对象和继承,所考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题,通过获取对象的状态信息得到输出或实现过程控制。
    
    
  12. 面向对象技术的基本概念是什么,三个基本特征是什么?

    基本概念:类、对象、继承
    基本特征:封装、继承、多态
    
    封装:将低层次的元素组合起来形成新的、更高实体的技术
    继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承
    多态:允许将子类类型的指针赋值给父类类型的指针
    
    
  13. C++空类默认有哪些成员函数?

    默认构造函数、析构函数、复制构造函数、赋值函数
    
    
  14. 类成员函数的重载、覆盖和隐藏区别?

    类成员函数的重载
    (1)具有相同的作用域(即同一个类定义中)(2)函数名字相同
    (3)参数类型,顺序 或 数目不同(包括const参数和非const函数)
    (4)virtual关键字可有可无。虚函数是基类希望派生类重新定义的函数,派生类重新定义基类虚函数的做法叫做覆盖;
    重载就在允许在相同作用域中存在多个同名的函数,这些函数的参数表不同。重载的概念不属于面向对象编程,编译器根据函数不同的形参表对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。
    重载的确定是在编译时确定,是静态的;虚函数则是在运行时动态确定。
    
    
    C++成员函数的覆盖
    (1)不同的作用域(非别位于派生类和基类中)(2)函数名称相同
    (3)参数列表完全相同;
    (4)基类函数必须是虚函数。
    
    
    隐藏是指派生类的成员函数遮蔽了与其同名的基类成员函数,具体规则如下:
    (1) 派生类的函数与基类的函数同名,但是参数列表有所差异。此时,不论有无virtual关键字,基类的函数在派生类中将被隐藏。(注意别与重载混合)
    (2)派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。此时,基类的函数在派生类中将被隐藏。(注意别与覆盖混合)
    
    
  15. 内联函数和宏函数的区别,相较于宏函数内联函数的优点是什么?

    C++中推荐使用内联函数替代宏代码片段
    内联函数和宏的区别在于:宏是由预处理器对宏进行替换的,而内联函数是通过编译器控制实现的。而且内联函数是真正的函数,只是在需要用到的时候内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。所以可以像调用函数一样来调用内联函数,而不必担心会产生像宏出现的问题。
    
    
  16. const关键字的作用

    C++中的const关键字的作用:
    	1. 当碰见 const声明时在符号表中放入常量
    	2. 编译过程中若发现使用常量则直接以符号表中的值替换
    	3. C++中的const是一个真正意义上的常量
    具体使用举例:
    	int * const p; //p指针的指向不能变
    	int const * q; //q指向的内存空间的值不能变
    	int const * const r; //r的指向和其指向的内存空间的值都不能变
    
    
  17. 引用的意义

    引用在c++中的内部实现是一个指针常量
    Type& name;  <----> Type* const name;  (注意这里const在*的右边修饰的是指针的指向)
    
    
    //代码验证
    #include <stdio.h>
    
    struct TRef
    {
        char& r;
    };
    
    int main(int argc, char *argv[])
    { 
        char c = 'c';
        char& rc = c;
        TRef ref = { c };
        
        printf("sizeof(char&) = %d\n", sizeof(char&));
        printf("sizeof(rc) = %d\n", sizeof(rc));
        
        printf("sizeof(TRef) = %d\n", sizeof(TRef));
        printf("sizeof(ref.r) = %d\n", sizeof(ref.r));
    
        return 0;
    }
    
    
    ubuntu14.04执行结果:
    sizeof(char&) = 1
    sizeof(rc) = 1
    sizeof(TRef) = 4
    sizeof(ref.r) = 1
    
    
     char c = 'c';
    00EA139E  mov         byte ptr [c],63h  
        char& rc = c;
    00EA13A2  lea         eax,[c]  
    00EA13A5  mov         dword ptr [rc],eax  
    @从汇编语句更加印证了引用rc是一个指针
    
    
  18. 引用和指针

    从使用c++的角度看
    - 引用与指针没有任何关系
    - 引用是变量的新名字,操作引用就是操作对应的变量
    从c++ 编译器的角度来看
    - 为了支持新概念"引用"必须要有一个有效的解决方案,也就是使用指针常量来实现
    - 因此 "引用" 在定义时必须初始化
    引用在c++中的内部实现是一个指针常量
    C++编译器在编译过程中用指针常量作为引用的内部实现,因此引用的空间大小与指针相同
    从实用的角度,引用只是一个别名,C++为了实用性而隐藏了引用的存储空间这一细节
    
    
  19. C++中结构体和类的区别

    最本质的一个区别就是默认的访问控制: 
    默认的继承访问权限,struct是public的,class是private
    
    
    //测试demo
    struct A
    {
      char a;
    };
    struct B : A
    {
      char b;
    };
    //如果都将上面的struct改成class,那么B是private继承A的,这就是默认的继承访问权限
    
    
  20. 局部变量和成员变量的区别

    1.定义的位置不一样
    	局部变量:在函数的内部
    	成员变量:在函数的外部,直接写在类当中
    2.作用范围不一样
    	局部变量:只有函数当中才可以使用,出了函数就不能再用了
    	成员变量:整个类都可以通用
    3.默认值不一样
    	局部变量:没有默认值,如果要想使用,必须手动进行赋值
    	成员变量:如果没有赋值,会有默认值,规则和数组一样
    4.内存的位置不一样
    	局部变量:位于栈内存
    	成员变量:位于堆内存
    5.生命周期不一样
    	局部变量:函数被调用的时候才会被定义,随着函数进栈而诞生,随着函数出栈而消失
    	成员变量:随着对象的创建而诞生,随着对象被delete而消失
    
    
  21. new和malloc的区别,delete和free的区别

    1. new和delete是C++中的关键字,malloc和free是函数
    2. new以类的字节大小开辟内存空间,malloc是以用户传递进来的字节大小开辟内存空间
    3. 使用new关键字创建一个新的对象时,会自动调用构造函数,malloc不会
    4. 使用delete释放时会自动调用析构函数,free不会
    
    
  22. 下面关于拷贝构造函数的说法哪一个是正确的?

    A. 给每一个对象拷贝一个构造函数

    B. 有一个默认的拷贝构造函数

    C. 不能拷贝队列

    D. 以上结果都正确

    答案:B
    解析:拷贝构造函数的含义是,参数为 const class_name& 的构造函数,不是给每一个对象拷贝一个构造函数所以A错误。这里的队列就是数据结构课程里的队列可以用链表和顺序表实现,拷贝构造函数可以拷贝队列,只不过如果说此队列在堆区开辟内存空间,使用默认的拷贝构造函数,浅拷贝情况下拷贝堆区的地址会是一个隐藏的bug。如果没有定义拷贝构造函数,编译器会默认提供一个拷贝构造函数B正确。
    
    
  23. 现有以下代码,则编译时会产生错误的是:

    struct Test
    {
          
          
    	Test (int){
          
          }
    	Test(){
          
          }
    	void fun(){
          
          }
    };
    
    int main()
    {
          
          
    	Test a(1); //语句1
    	a.fun();   //语句2
    	Test b();  //语句3
    	b.fun();   //语句4
    	return 0}
    
    

    A. 语句1 B.语句2 C.语句3 D.语句4

    答案:D
    解析:Test b()是不正确的,因为它不需要预先赋值,不像Test a(1)需要预先赋值,所以Test b即可。但是在程序中这些错误在编译时是检测不出来的,出错的是语句4  b.fun();  ,它是编译不过去的。
    
    
  24. 这个类声明正确吗?为什么?

    class A
    {
    	const int Size = 0;
    }
    
    
    //答案:此程序存在成员变量的问题,常量必须在构造函数的初始化列表里面初始化或者将其设置成static。
    //正确的程序如下:
    class A
    {
    	A()
    	{
    		const int Size=9;
    	}
    };
    //或者
    class A
    {
    	static const int Size=9;
    };
    
    
  25. 已知String类定义如下,请尝试写出类的成员函数实现

    class String
    		{
    			public;
    			String(const char *str = NULL); //通用构造函数
    			String(const String &anather);  //拷贝构造函数
    			~String();                      //析构函数
    			String & oprater = (const String &rhs); //赋值函数
    			private;
    			char *m_data;    //用于保存字符串
    		}
    
    
    //通用构造函数
    	String::String(const char *str)
    	
    	{
    	if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
    		{
    		m_data = new char[1] ;
    		m_data[0] = '\0' ;
    		}
    	else
    		{
    		m_data = new char[strlen(str) + 1];
    		strcpy(m_data,str);
    		}
    	} 
    	
    	//拷贝构造函数
    	String::String(const String &another)
    	{
    		m_data = new char[strlen(another.m_data) + 1];
    		strcpy(m_data,other.m_data);
    	}
    	
    	//赋值函数
    	String& String::operator =(const String &rhs)
    	{
    		if ( this == &rhs)
    			return *this ;
    		delete []m_data; //删除原来的数据,新开一块内存
    		m_data = new char[strlen(rhs.m_data) + 1];
    		strcpy(m_data,this.m_data);
    		return *this ;
    	}
    	
    	//析构函数
    	String::~String()
    	{
    		delete []m_data ;	
    			
    	}
    
    
  26. 哪一种成员变量可以在同一个类的实例之间共享?

    答案
    必须使用静态成员变量在一个类的所有实例间共享数据。如果想限制对静态成员变量的访问,则必须把它们声明为保护型或私有型。不允许用静态成员变量去存放摸一个对象的数据。静态成员数据是在这个类的所有对象间共享的。
    
    
  27. 当程序中存在多个对象的时候,如何确定这些对象的析构顺序?

    答案:
    单个对象创建时构造函数的调用顺序
    	调用父类的构造过程
    	调用成员变量的构造函数(调用顺序与声明顺序相同)
    	调用类自身的构造函数	
    		析构函数与对应构造函数的调用顺序相反
    多个对象析构时
    	析构顺序与构造顺序相反
    
    
  28. C++中哪些运算符可以重载?哪些不可以重载?

    可以重载的运算符
    双目算术运算符:+ (加),-(减),*(乘),/(除),% (取模)
    关系运算符:==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
    逻辑运算符:||(逻辑或),&&(逻辑与),!(逻辑非)
    单目运算符:+ (正),-(负),*(指针),&(取地址)
    自增自减运算符:++(自增),--(自减)
    位运算符:| (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
    赋值运算符:=, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
    空间申请与释放:new, delete, new[ ] , delete[]
    其他运算符:()(函数调用),->(成员访问),,(逗号),[](下标)
    
    
    不可以重载的运算符
    .:成员访问运算符
    .*, ->*:成员指针访问运算符
    :::域运算符
    sizeof:长度运算符
    ?::条件运算符
    #: 预处理符号
    
    
  29. 什么是多态?

    答案
    开门、开窗户、开电视。在这里的“开”就是多态!
    多态性可以简单地概括为“一个接口,多种方法”,在程序运行的过程中才决定调用的函数。多态性是面向对象编程的核心概念。
    多态(polymorphisn),按字面意思就是“多种形状”。多态性是允许你将父对象设置成为和它的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说,就是一句话,允许将子类类型的指针赋值给父类类型的指针。多态性在C++中是通过虚函数实现的。
    
    
  30. 虚函数的入口地址和普通函数有什么不同?【英国某著名计算机图形图像公司面试题】

    答案:
    	每个虚函数都在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令(实际上就是保存了它的入口地址)。当一个包含虚函数的对象(注意,不是对象的指针)被创建的时候,它在头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管你是用什么指针调用的,它先根据vtable找到入口地址再执行,从而实现了“动态联编”。而不像普通函数那样简单地跳转到一个固定地址。
    
    
  31. 重载和覆盖有什么不同?

    答案
    虚函数总是在派生类重写基类的虚函数,重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但是很少有编译器支持这个特性)。
    “重载”,是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数既可以接收整型数作为参数,也可以接收浮点数作为参数。
    重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。
    
    
  32. 如果一个圆角矩形有直边和圆角,那么圆角矩形也就多重继承了圆形和矩形,而圆形和矩形又都是从shape类中里继承的。请问,当你创建一个圆角矩形类时,共创建了多少个shape?

    答案
    如果圆形类和矩形类都不是用关键字virtual继承shape类,那么生成两个shape:一个为圆形,一个为矩形类。如果圆形类和矩形类都是用关键字virtual继承类,那么生成一个共享的shape。
    
    
  33. 以下代码的输出结果是什么?【德国某著名电子/通信/IT企业】

    #include <iostream>
    using namespace std;
    class A{
    	public:
    	virtual void f(){
    		cout << "A" << endl;
    	}
    };
    
    class B : public A{
    	public:
    	virtual void f(){
    		cout << "B" << endl;
    	}
    };
    
    int main()
    {
    	A* pa = new A();
    	pa->f();
        B* pb = (B*)pa;
    	pb->f();
    	delete pa, pb;
    	pa = new B();
    	pa->f();
    	pb = (B*)pa;
    	pb->f();
    	return 0;
    }
    
    

    A. AABA B. AABB C. AAAB D. ABBA

    答案:B
    解析:
    	这是一个虚函数覆盖虚函数的问题。
    	A类里的f函数是一个虚函数,虚函数是被子类同名函数所覆盖的。而B类里的f函数也是一个虚函数,它覆盖A类f函数的同时,也会被它的子类覆盖。但是在B* pb = (B*)pa; 里面,该语句的意思是转化pa为B类型并新建一个指针pb, 将pa复制到pb。但这里有一点需要注意,就是pa的指针始终没有发生变化,所以pb也是指向pa的f函数。这里并不存在覆盖的问题。
    	delete pa, pb;删除了pa和pb所指向的地址,但pa、pb指针并没有删除,也就是我们通常说的悬浮指针。现在重新给pa指向新地址,所指向的位置是B类的,而pa指针类型是A类的,所以就产生了一个覆盖。pa->f();的值是B。
    	pb=(B*)pa;转化pa为B类指针给pb赋值,但pa所指向的f函数是B类的f函数,所以pb所指向的f函数是B类的f函数。pb->f();的值是B
    
    

猜你喜欢

转载自blog.csdn.net/weixin_48430195/article/details/108669783