C++ 基础知识 面试题(二)

前篇见C++ 基础知识 面试题(一)_绿风天空的博客-CSDN博客

16. const

使用`const`关键字可以确保变量的值在初始化后不能被修改,从而提供更强的类型安全和代码可读性。

常量指针:        int* const ptr = &value;

*ptr = 10; // 通过指针修改所指向的值
// ptr = nullptr; 无法修改指针的值,因为它是一个const指针
在上述示例中,`ptr`是一个`const`指针,它指向`value`变量。虽然通过`ptr`可以修改`value`的值,但无法修改`ptr`指针本身。

指向const类型变量的指针:        const int* ptr = &value;

// *ptr = 10; 无法通过指针修改所指向的值
ptr = nullptr; // 可以修改指针的值,因为它不是const指针
在上述示例中,`ptr`是一个指向`const int`的指针,它指向`value`常量。这意味着通过`ptr`无法修改`value`的值,但可以修改`ptr`指针本身。

指向`const`对象的常量指针:        const int* const ptr = &value;

通过`ptr`既无法修改`value`的值,也无法修改`ptr`指针本身。

声明常量成员函数:
class MyClass {
public:
    void myMethod() const;
};
`myMethod()`是一个常量成员函数。这意味着该函数不能修改类的成员变量(除非它们被声明为`mutable`),并且不能调用非常量成员函数

17.引用和指针的下区别:

1)指针可以在声明时不进行初始化,也可以在之后指向其他对象,而引用必须在声明时进行初始化,并且一旦初始化后不能再引用其他对象。
int* ptr = &value; // 指针的定义和初始化
int& ref = value; // 引用的定义和初始化

2)空值(null):指针可以具有空值(null),即指向空地址或未初始化的指针。而引用必须始终引用一个有效的对象,它不能为null。
 

3.)可以重新赋值:指针可以重新赋值,即可以指向其他对象或null。而引用在初始化后不能重新赋值,它始终引用同一个对象。

4) 内存管理:指针可以进行内存管理,可以使用`new`和`delete`来动态分配和释放内存。而引用只是对象的别名,它不负责内存管理,只是提供了一个对对象的简便访问方式。

5)空间占用:指针通常需要占用额外的空间来存储指针本身的地址值。而引用不需要额外的空间,它直接引用了对象。在编译时,引用被转换为对所引用对象的直接访问,而不是通过额外的存储空间来实现。

18.强制类型转换

子类不仅有自己的方法和属性,还有从父类继承的方法和属性。当进行向上转换时,从子类转换成父类,没有任何问题。但是进行向下转换时,从父类转换成子类,如果取调用子类才有的方法和属性,就会造成问题。所以c++增加了static_cast和dynamic_cast运用于继承关系类间的强制转化。

1)静态转换(static_cast):
   - 静态转换用于将一种类型转换为另一种具有相关性的类型,如基本数据类型之间的转换,或者父类指针/引用转换为子类指针/引用
   - 静态转换在编译时进行类型检查,不提供运行时的检查,因此需要开发者确保转换是安全的。

常见用法:

  (1)用于基本数据类型之间的转换

(2)把空指针转换成目标类型的空指针

(3)把任何类型的表达式类型转换成void类型

(4)用于类层次结构中父类和子类之间指针和引用的转换。上行转换时安全的,而下行转换时不安全的。

2)动态转换(dynamic_cast):
   - 动态转换用于在继承层次结构中进行类型转换,可以将基类指针或引用转换为派生类指针或引用。
   - 动态转换在运行时进行类型检查,如果转换不安全,则返回空指针(对于指针转换)或抛出std::bad_cast异常(对于引用转换)。会确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象。
 

3)重新解释转换(reinterpret_cast):
   - 重新解释转换用于将一种类型的位模式解释为另一种类型的位模式,可以将任意类型的指针或引用转换为其他类型的指针或引用。
   - 重新解释转换不进行任何类型检查,只是将位模式进行重新解释,因此需要非常小心使用。
 

4)常量转换(const_cast):
   - 常量转换用于去除指针或引用的常量属性,可以将const修饰的指针或引用转换为非const修饰的指针或引用。
   - 常量转换只能修改指针或引用的常量属性,不能修改对象本身的值。
 

19.预处理指令

头文件中的`#ifndef`、`#define`和`#endif`是预处理指令,用于防止头文件的重复包含。

当一个头文件被多个源文件包含时,如果没有适当的防范措施,可能会导致重复定义的错误。为了避免这种情况,可以使用条件编译指令来保证头文件只被编译一次。

首先,在头文件的开头使用`#ifndef`指令(意为"if not defined")检查一个宏是否已经被定义。如果这个宏已经被定义,则说明该头文件已经被包含过,可以直接跳过后续的内容。

```cpp
#ifndef HEADER_NAME_H
#define HEADER_NAME_H

// 头文件的内容

#endif
```

接下来,使用`#define`指令定义这个宏,以便在后续的编译过程中可以识别它。通常,宏的名称使用头文件的名称,加上一个唯一的后缀。

最后,在头文件的结尾使用`#endif`指令结束条件编译块。

这样,当第一个源文件包含头文件时,`#ifndef`指令会发现宏未定义,然后执行`#define`指令定义宏,并继续编译头文件的内容。当后续的源文件包含同一个头文件时,`#ifndef`指令会发现宏已经定义,直接跳过头文件的内容。

通过使用`#ifndef`、`#define`和`#endif`,可以确保头文件只被编译一次,避免了重复定义的错误。这是C++中常用的一种防范措施,被称为"头文件保护"或"头文件守卫"。

20. typedef 和 #define

typedef和#define都是C和C++中用于定义类型别名或宏的关键字,但它们有一些不同的特点和用法。

typedef是一个用于定义类型别名的关键字。它通过给一个已有类型起一个新的名称,使得我们可以使用这个新名称来代替原有类型。它的语法如下:

typedef 原有类型 新类型名;

typedef int Number;

这样,Number就成为了int类型的别名。我们可以使用Number来代替int类型进行变量的声明和使用。
Number num = 10;
 

#define是一个预处理指令,用于定义宏。它通过在代码中进行文本替换,将一个标识符替换为指定的文本内容。它的语法如下:
#define 宏名 替换文本
#define PI 3.14159

这样,每次在代码中使用`PI`时,预处理器会将其替换为`3.14159`。
double circleArea = PI * radius * radius;

#define还可以定义带参数的宏,类似于函数的宏
#define SQUARE(x) ((x) * (x))
这样,每次在代码中使用`SQUARE`宏时,预处理器会将其参数替换到宏定义中,并进行文本替换。

int result = SQUARE(5); // 替换为 (5) * (5) -> 25
 

#define的问题:

1)一个操作数如果在两处被用到,就会被求值两次。

如:int a=10,b=1;max(a++,b):使用宏返回值是11;使用普通函数返回10。

使用宏时宏展开后为:((a++)>(b)?(a++)):(b)).如果a>b,则a会被++两次。

2)没有括号导致的问题。

#define MINUS(a,b) a-b

当用在3*MINUS(a,b)/7时,是直接替换成3*a-b/7,所以要加括号,写成:

#define MINUS(a,b) (a-b)

3)定义变量产生的问题

#define POINTER int*

当用在定义变量时, POINTER a, b;

a是int*, b是int。

所以定义变量时,要用typedef。

typedef POINTER (int*);

总结:

typedef用于定义类型别名,而#define用于定义宏。typedef定义的类型别名是静态的,而#define定义的宏是在预处理阶段进行文本替换的。

21.虚函数

C++的虚函数是一种特殊的成员函数,它可以在基类中声明并在派生类中重写。通过在函数前面添加关键字“virtual”,可以将其声明为虚函数。派生类可以通过重写虚函数来改变基类中定义的虚函数的行为。

需要虚函数的主要原因是实现多态性,多态性是面向对象编程的一个重要概念,它允许在运行时确定哪个函数将被调用,而不是在编译时确定。当基类指针指向派生类对象时,可以通过调用虚函数来调用派生类中的函数,而不是基类中的函数。这使得程序可以更加灵活和可扩展。例如,使用虚函数可以实现基于抽象类的接口,这样派生类就可以根据需要实现自己的行为,并且可以通过基类指针来访问这些行为。

虚函数也允许在基类中定义通用的行为,而在派生类中重写特定的行为。这种行为可以通过继承来实现,而不需要在派生类中重新实现基类中已经定义过的函数。

22.纯虚函数(Pure Virtual Function)

C++的纯虚函数是一种在基类中声明但没有实现的虚函数。它通过在函数声明后加上`= 0`来声明。纯虚函数没有函数体,只是用来作为接口的定义。

抽象类(Abstract Class)是包含至少一个纯虚函数的类。抽象类不能被实例化,只能被用作其他类的基类。抽象类的主要目的是定义接口,而不是提供具体的实现。

抽象类通过将纯虚函数定义为接口,强制派生类提供具体的实现。派生类必须实现基类中的纯虚函数,否则派生类也会变成抽象类。

抽象类可以包含非纯虚函数和数据成员,它们可以有自己的实现。但只有纯虚函数是没有实现的,它们需要在派生类中进行具体的实现。

抽象类的主要作用是为了实现接口的定义和规范,通过强制派生类实现纯虚函数,实现多态性和代码的可扩展性。抽象类也可以包含具体的实现,提供共享的代码逻辑。

23.static关键字

静态变量(Static Variables):在函数内部使用static修饰的变量称为静态变量。静态变量仅在第一次调用时初始化,并在程序的整个生命周期中保持其值,直到程序结束。静态变量存储在静态数据区,而不是栈上。静态变量可以在函数内部和类内部使用。

静态成员变量(Static Member Variables):在类中使用static修饰的成员变量称为静态成员变量。静态成员变量属于整个类,而不是类的实例。所有类的实例共享同一个静态成员变量。静态成员变量在类的定义外部初始化,并且可以通过类名和作用域解析运算符::访问。

静态函数(Static Functions):在类中使用static修饰的成员函数称为静态函数。静态函数不依赖于类的实例,可以直接通过类名调用,而无需创建对象。静态函数只能访问类的静态成员变量和其他静态函数,不能访问非静态成员变量和非静态函数。

静态普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。为了防止与他人命名空间里的函数重名,可以将函数定位为 static。

静态类(Static Classes):在C++中,类不能直接声明为静态,但可以通过将构造函数和析构函数设为私有,并将类的所有成员函数和成员变量设为静态来模拟静态类。静态类不能被实例化,只能通过类名和作用域解析运算符::来访问其成员。

24.inline关键字

在C++中,inline是一种函数修饰符,用于声明内联函数。

内联函数是一种编译器优化的手段,它的作用是将函数的定义插入到调用处,而不是通过函数调用的方式执行函数。

使用inline修饰函数可以提高程序的执行效率,特别是对于一些简单的函数。内联函数的优点包括:

  • 减少函数调用的开销:函数调用涉及保存和恢复现场、参数传递等操作,使用内联函数可以减少这些开销。
  • 提高程序的执行速度:内联函数的代码被插入到调用处,避免了函数调用的开销,从而提高程序的执行速度。
  • 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
  • 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
  • 内联函数在运行时可调试,而宏定义不可以。

缺点包括:

  • 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
  • inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
  • 编译器对于是否将函数作为内联函数进行优化是有一定的自由度的,它可能会根据一些因素(如函数的复杂性、函数的调用频率等)来决定是否进行内联优化。因此,使用inline修饰函数并不一定能够确保函数被内联。

需要注意的是,内联函数适用于函数体较短、调用频率较高的情况对于复杂的函数或者递归函数,不适合使用内联函数。​​​​​​​

在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数

若在类外定义函数,则需要显示写出inline来显式内联,不写则默认为不是内联函数

使用
// 类内定义,隐式内联
class A {
    int doA() { return 0; }         // 隐式内联
}

// 类外定义,需要显式内联
class A {
    int doA();
}
inline int A::doA() { return 0; }   // 需要显式内联
25.虚函数(virtual)可以是内联函数(inline)吗?​​​​​​​

虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联

内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值