栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c++面试题目-第一部分

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++面试题目-第一部分

2022-05-05日
一.请说一下指针和引用的区别和联系?
指针本身是一个变量,存储的是变量的地址,而引用则是对一个变量起别名。指针可以不赋初值:例如int *a。引用:必须要进行初始化,例如int b=10,int &a=b。一旦引用被绑定到该对象时,之后就无法绑定到其他对象上去。引用只能绑定到对象上,而不能绑定到字面值上,例如int &a=10,这种写法是错误的。
二.const关键字:被const关键字修饰之后的变量为只读变量,注意不是常量哦。它无法被修改了。
1.常量指针:通过指针不能修改所指对象的值。

int tmp=10;
const int *a=&tmp;
int const *a=&tmp;

2.指针常量:指针指向的不可改变性,一旦被初始化后,就无法指向其他对象了。

int tmp=10;
//const靠近变量类型int*
int *const a=tmp;

以下代码:

int main()
{
    int a = 10;
    int b = 20;
    const int* tmp = &a;
    *tmp = 15; 错误写法,不能通过常量指针改变所指变量的值
    a = 15; 变量本身是可以修改的
    tmp = &b; 正确,可以改变所指对象的值
    int* const tmp2 = &b;
    tmp2 = &a; 错误,无法改变指针的指向
    *tmp2 = 30;正确,可以修改所指对象的值
    return 0;
}

三.define和typdef的区别和联系。
博文链接:c语言中typdef的用法
define和typdef都可以用来给变量起别名,其中define只是简单的字符串替换,不涉及类型的检查,发生在编译的预处理阶段,可以防止头文件被重复引用。typdef:是要进行数据类型的检查的,而且发生在编译和运行阶段。typdef可以保证几个连续的变量为同一个类型,而define则不可以。

#define PTR_INT int *
PTR_INT p1, p2;

替换之后,p1是int*,而p2则是int类型

typedef int * PTR_INT
PTR_INT p1, p2;

采用typedef,p1和p2都是int*类型

四.define 和 inline 的区别
内联函数
1.define是宏定义,发生在预处理阶段,不经过类型检查,不安全。
2.inline将内联函数编译插入到被调用的位置上去,减少了函数压栈,跳转,返回的操作,因此省去了函数调用时的开销。
3.inline同样也会进行类型的检查,相比较define来说是安全的。
4.inline说明对编译器来说只是一种建议,编译器可以选择忽略这个建议,如果代码非常长的话,编译器还是会将它看作普通函数来处理。
5. 如果在类中直接定义,不需要用inline修饰,编译器自动化为内联函数。

五.C++中的函数的重载,函数的覆盖(重写),函数的隐藏的区别
函数的重载:在同一个作用域内,函数的名称相同,参数不同,通过提供不同的实参就可以调用不同的同名的函数。
函数的覆盖:(通过父类的指针或者引用可以调用子类对应的函数)

  1. 两个函数必须出现在基类和子类不同的作用域内,
  2. 函数的名称,参数,返回值都必须要相同
  3. 两个函数都必须要是虚函数。

函数的隐藏:子类的同名函数会隐藏掉父类的同名函数,需要满足以下两个条件:

  1. 子类的函数和父类的函数的名称相同,参数不相同,此时无论有无virtual关键字,父类的函数都会被子类隐藏。(最好不要加virtual)
  2. 函数的名称相同,参数相同,但是父类函数没有添加virtual关键字。

如下所示:子类的Drive函数隐藏了父类的Drive函数,因此子类对象调用Drive函数时,对应的父类的Drive函数就会被隐藏。

class Car {
public:
    void Drive() { cout << "I am Cars" << endl; }
};
class Benz :public Car {
public:
    void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
    Benz t1;
    Car* father=new Benz;
    father->Drive();//I am Cars
    t1.Drive();//"Benz-舒适"
    return 0;
}

老师在课上讲的那样:19节课和20节课,值得再次看一遍。
今天的收获:因为基类的函数是虚函数,因此当基类的指针指向子类时,本质上是虚函数表中基类函数的地址被子类函数的地址覆盖掉了。
多态的意义是什么呢?值得思考,还是需要多写代码去好好感受呀~
而重写也很道理,假设一个子类继承自许多类,如果没有重写机制,那么它很可能不知道要听那个爸爸的话。因此如果我有一个和你一样的函数,那么我就听我自己的咯。
注意,子类调用自己的函数,不存在多态现象。内容来自于:高质量的c++编程指南
参考这篇博文:三者区别

六、new 和malloc的区别
malloc函数:动态的分配内存,分配的内存是在堆空间上开辟的。发生异常的时候指针会返回NULL,需要显示的指定分配的内存空间大小, 返回值是void*,需要强转成对应的指针类型。
new:是c++的的运算符,分配内存之后还会调用构造函数,对对象进行初始化。发生异常的时候,会抛出bad_alloc,不需要显示指定内存大小,返回值为具体的类型指针。
new函数捕获异常
七、const和constexpr
const:可以分为顶层const和底层const
顶层const: 指的是指针(任意的对象本身)是常量,不可改变 (指针常量) ,类似于字符常量,它是只读的,不可以被改变。
底层const: 指针所指的对象是不可以被改变的。
例如:const int a=10,我们不能改变a的值,因此它是一个顶层const。const int *b=&a,b是一个底层const,因为不能通过b去修改a的值。
注意的点:
当对象之间进行赋值的时候,底层const和顶层const的区别是很大的。
当对象本身是顶层const时,执行拷贝操作不会影响被拷贝对象的值。例如const int b = 10; int a = b;无论b是否是一个常量,a拷贝b之后,a的值的改变是不会影响b的值的,因为a和b是被分配了不同的内存空间。
当对象包含底层const时,两个对象必须都具备相同的底层const类型,。一般情况下,非常量可以转换为常量,反之则不可以。如下所示p1=p2的这种情况。
怎么理解比较简单一点:拷出对象的变化对于被拷贝对象的取值是否有影响,如果会改变原始对象的值,那么编译器是不允许的,这种情况一般出现在包含常量指针的情况下。

constexpr变量:可以用来声明一个变量为常量表达式。
常量表达式:在编译期间就可以被确定的表达式
const int sz=get_size()并不是一个常量表达式,因为它只有运行的时候才可以被确定 。函数调用的话只要运行时期,才可以被确定。
constexpr只能定义编译器常量,而const也可以定义运行期常量。
编译期常量是由编译器在编译时通过计算来确定常量的值,然后在代码中直接进行替换。
constexpr可以用来修饰函数,使得函数成为一个常量表达式函数,函数的形参和返回值都为字面值(一般为数字),在编译的时候就会被隐式转换为内联函数。
参见这篇文章:运行期常量和编译器常量
例如文章中举出的例子:getconst函数被定义为const函数,即便它的返回值式1的话,在数组int arr[getconst()] = { 0 }定义时会报错。而如果函数被定义为constexpr函数,那么编译器就可以在编译时期对getconst表达式进行值计算,从而将其视为一个编译时期的常量(可能)。
const用法:
const用法
1.const修饰类成员变量时,必须通过初始化列表或者类内初始化(c++11)的方式为成员提供初值。类似于const int a=10,必须在初始化的时候定义。
注意,成员是引用,const或者属于未提供默认构造函数的类类型,必须使用构造函数初始化列表的方式。一般都使用这种方式。

2.const修饰类成员函数时,代表成员函数的值不能被修改。本质上是限定了this指针为const test * const this : 即 this 不能够被修改。
3.const_cast 去除掉变量的const属性,方便赋值。

八、static用法:
1.static可以修饰局部变量,局部变量本身存储在栈区,被static修饰之后存储在静态存储区,如果没有被显示初始化,将被赋值为0。
2.staic也可以修饰全局变量,被static修饰的全局变量,只可以在本文件中使用。如果不加static,使用extern关键字之后,该变量可以被多个文件使用,它可以有效的解决不同文件中全局变量重名的问题。(手把手教c语言245页)
3.如果某个类的成员变量被static修饰之后,该成员变量不属于某个对象,而是由该类的所有对象共享,由于它不属于某个类,因此只能在类外进行初始化,可以在类内进行声明。
4.static修饰类的成员函数时,不能访问非静态成员和非静态成员函数(因为静态函数没有this指针,因此不能调用非静态成员函数)
静态成员函数不能被声明成const,没有this指针哦!

class test
{
public:
    test(int a=2, int b=3, int c=4) :m_a(a), m_b(b), m_c(c) {  }
    void get_m_c() { cout << m_c << endl; }
    static void stat_fun();//加上const就错误啦!
    非静态成员函数可以访问静态成员函数,毕竟静态成员函数属于所有对象嘛!
    void norm_fun() { stat_fun(); }
private:
    int m_a;
    int m_b;
    const int m_c = 10;
    static int m_d;
};
类外初始化,由于静态变量不属于类内成员,不能在类内初始化。
int test::m_d = 20;
void test::stat_fun()
{
    错误写法,静态成员函数没有this指针。
    this->get_m_c(); 
    cout << this->m_b << endl; 
    cout <
    使用作用域运算符直接访问静态成员函数
    test::stat_fun();
     也可以通过对象来访问某个静态成员函数
    test t1;
    t1.stat_fun();
}

九、extern: 外部变量,针对于全局变量来说的:不同的文件中如果包含相同的全局变量,它可以扩展全局变量的范围。

十、前置++和后置++:后置++会创建临时对象,会调用拷贝构造函数和析构函数,损耗效率。而前置++则直接返回的是当前对象。最好使用前置++。

十一、atomic是一个模板类型,由于a++,int a=b,这样的操作并不是原子操作,在多线程的情况下,可能会使得最终的结果不正确。使用atomic生成的数据,在加减的过程中,可以保证是原子操作。

十二、c++三大特性(封装,继承,多态)

访问限定符:

  1. public访问:通过对象可以在类外访问公有的成员,对外是可见的。
  2. protected访问:不能通过对象直接访问保护类的成员,对外是不可见的,它可以通过公有继承/保护继承的方式被子类对象所使用。
  3. private访问:成员对外是不可见的,通过对象不可以直接访问。

继承方式: 公有继承,保护继承,私有继承。
三大特性:
1.封装:可以隐藏对象的实现细节,与此同时它可以对外提供访问的方式,便于调用者的使用,提高了安全性.例如:将公有的数据和方法使用public修饰,而将不希望被访问的数据使用private来修饰。

2.继承:是提高代码复用的一种方式,新的类可以获得原有类的所有功能,并且在不需要编写原来类的情况下对这些功能进行扩展,增加新的功能,这样的类叫做派生类。

3.多态:完成某个行为时,不同的对象去完成会产生不同的状态。不同的对象去调用同一个函数的时候,产生了不同的行为。
多态形成的条件:

  1. 父类的指针或者引用指向子类
  2. 被调用的函数必须要是虚函数,而且子类必须对父类的函数进行重写。

多态总结参见这篇博文:多态原理
虚继承:为了解决菱形继承中数据冗余和二义性问题:c++中提出了虚继承,派生类中的间接的基类成员只会保留一份。
虚基类:;例如:B继承A,C继承A,D又分别继承B和C,构成了菱形继承。这里的A就是一个虚基类。

class A {
public:
 int _a;
};
// class B : public A
class B : virtual public A {
public:
 int _b;
};
// class C : public A
class C : virtual public A {
public:
 int _c;
};
class D : public B, public C {
public:
 int _d;
};
int main()
{
 D d;
 d.B::_a = 1; 
 d.C::_a = 2; //c重新修改了,所以取值为2
 d._b = 3;
 d._c = 4;
 d._d = 5;
 return 0; }

内存模型如下所示:其中0x0059F720是d的地址,d当作存储了两张虚函数表:保存的是基类成员中偏移量的地址,第一个地址是0x00d2b6f4:通过该地址可以找到对应的偏移量为14(十进制就是20),即从f4的位置开始向后偏移20个单位就可以找到A中的_a数据成员2了。同理从0x00d2ab44的位置可以找到偏移量为c(12),向后偏移12个字节就可以找到基类A的成员_a,它的取值为2。

虚拟继承存储数据的时候并没有节省空间,反而增加了4个字节的空间。但是我们需要思考这样一个问题:假设D的父类B和C中的成员m_a各自取值不同,例如B:m_a=2,C:m_a=4,那么D继承之后,就会出现数据二义性的问题,因为它既可以选择B中的m_a,也可以选择C中的m_a。采用虚拟继承的方法:确保了子类的成员只有1种取值的可能性,杜绝了数据二义性的这种可能情况。不过还是尽量不要使用菱形继承。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/873544.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号