Skip to content

Latest commit

 

History

History
723 lines (528 loc) · 40.3 KB

File metadata and controls

723 lines (528 loc) · 40.3 KB

目录

1.C/C++中面向对象的相关知识

面向对象程序设计(Object-oriented programming,OOP)有三大特征 ——封装、继承、多态。

封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 关键字:public, protected, private。不写默认为 private。

  1. public 成员:可以被任意实体访问。

  2. protected 成员:只允许被子类及本类的成员函数访问。

  3. private 成员:只允许被本类的成员函数、友元类或友元函数访问。

继承:基类(父类)——> 派生类(子类)

多态:即多种状态(形态)。简单来说,我们可以将多态定义为消息以多种形式显示的能力。多态是以封装和继承为基础的。

C++ 多态分类及实现:

  1. 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载

  2. 子类型多态(Subtype Polymorphism,运行期):虚函数

  3. 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板

  4. 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换

2.C/C++中程序的开发流程?

开发一个C++程序的过程通常包括编辑、编译、链接、运行和调试等步骤。

编辑:编辑是C++程序开发过程的第一步,它主要包括程序文本的输入和修改。任何一种文本编辑器都可以完成这项工作。当用户完成了C++程序的编辑时,应将输入的程序文本保存为以.cpp为扩展名的文件(保存C++头文件时应以.h为扩展名)。

编译:C++是一种高级程序设计语言,它的语法规则与汇编语言和机器语言相比更接近人类自然语言的习惯。然而,计算机能够“看”懂的唯一语言是汇编语言。因此,当我们要让计算机“看”懂一个C++程序时,就必须使用编译器将这个C++程序“翻译”成汇编语言。编译器所做的工作实际上是一种由高级语言到汇编语言的等价变换。

汇编:将汇编语言翻译成机器语言指令。汇编器对汇编语言进行一系列处理后最终产生的输出结构称为目标代码,它是某种计算机的机器指令(二进制),并且在功能上与源代码完全等价。保存源代码和目标代码的文件分别称为源文件和目标文件( .obj)。

链接:要将汇编器产生的目标代码变成可执行程序还需要最后一个步骤——链接。链接工作是由“链接器”完成的,它将编译后产生的一个或多个目标文件与程序中用到的库文件链接起来,形成一个可以在操作系统中直接运行的可执行程序。(linux中的.o文件)

运行和调试:我们接下来就可以执行程序了。如果出现问题我们可以进行调试debug。

3.C/C++中的new和malloc有什么区别?

new和malloc主要有以下三方面的区别:

  1. malloc和free是标准库函数,支持覆盖;new和delete是运算符,支持重载。

  2. malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。

  3. malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。

4.C/C++中面向对象和面向过程的区别?

面向对象(Object Oriented Programming,OOP)编程模型首先抽象出各种对象(各种类),并专注于对象与对象之间的交互,对象涉及的方法和属性都封装在对象内部。

面向对象的编程思想是一种依赖于类和对象概念的编程方式,一个形象的例子是将大象装进冰箱:

  1. 冰箱是一个对象,大象也是一个对象。
  2. 冰箱有自己的方法,打开、存储、关闭等;大象也有自己的方法,吃、走路等。
  3. 冰箱有自己的属性:长、宽、高等;大象也有自己的属性:体重、高度、体积等。

面向过程(Procedure Oriented Programming,POP)编程模型是将问题分解成若干步骤(动作),每个步骤(动作)用一个函数来实现,在使用的时候,将数据传递给这些函数。

面向过程的编程思想通常采用自上而下、顺序执行的方式进行,一个形象的例子依旧是将大象装进冰箱:

  1. 打开冰箱。
  2. 把大象装进冰箱。
  3. 关闭冰箱。

总结来说面向对象和面向过程的区别:

  1. 安全性角度。面向对象比面向过程安全性更高,面向对象将数据访问隐藏在了类的成员函数中,而且类的成员变量和成员函数都有不同的访问属性;而面向过程并没有办法来隐藏程序数据。

  2. 程序设计角度。面向过程通常将程序分为一个个的函数;而面向对象编程中通常使用一个个对象,函数通常是对象的一个方法。

  3. 逻辑过程角度。面向过程通常采用自上而下的方法;而面向对象通常采用自下而上的方法。

  4. 程序扩展性角度。面向对象编程更容易修改程序,更容易添加新功能。

5.C/C++中常用容器功能汇总

vector(数组)

vector是封装动态数组的顺序容器。

成员函数:

  1. at():所需元素值的引用。
  2. front():访问第一个元素(返回引用)。
  3. back():访问最后一个元素(返回引用)。
  4. beign():返回指向容器第一个元素的迭代器。
  5. end():返回指向容器末尾段的迭代器。
  6. empty():检查容器是否为空。
  7. size():返回容器中的元素数。
  8. capacity():返回当前存储空间能够容纳的元素数。
  9. clear():清除内容。
  10. insert():插入元素。
  11. erase():擦除元素。
  12. push_back():将元素添加到容器末尾。
  13. pop_back():移除末尾元素。
  14. *max_element(v.begin(), v.end()):返回数组最大值。
  15. *min_element(v.begin(), v.end()):返回数组最小值。

queue(队列)

queue是容器适配器,他是FIFO(先进先出)的数据结构。

成员函数:

  1. front():访问第一个元素(返回引用)。
  2. back():访问最后一个元素(返回引用)。
  3. empty():检查容器是否为空。
  4. size():返回容器中的元素数。
  5. push():向队列尾部插入元素。
  6. pop():删除首个元素。

deque(双端队列)

deque是有下标顺序容器,它允许在其首尾两段快速插入和删除。

成员函数:

  1. front():访问第一个元素(返回引用)。
  2. back():访问最后一个元素(返回引用)。
  3. beign():返回指向容器第一个元素的迭代器。
  4. end():返回指向容器末尾段的迭代器。
  5. empty():检查容器是否为空。
  6. size():返回容器中的元素数。
  7. clear(): 清除内容。
  8. insert():插入元素。
  9. erase():擦除元素。
  10. push_back():将元素添加到容器末尾。
  11. pop_back():移除末尾元素。
  12. push_front():插入元素到容器起始位置。
  13. pop_front():移除首元素。
  14. at():所需元素值的引用。

set(集合)

集合基于红黑树实现,有自动排序的功能,并且不能存放重复的元素。

成员函数:

  1. begin()--返回指向第一个元素的迭代器。

  2. clear()--清除所有元素。

  3. count()--返回某个值元素的个数。

  4. empty()--如果集合为空,返回true。

  5. end()--返回指向最后一个元素的迭代器。

  6. erase()--删除集合中的元素。

  7. find()--返回一个指向被查找到元素的迭代器。

  8. insert()--在集合中插入元素。

  9. size()--集合中元素的数目。

unordered_set(无序集合)

无序集合基于哈希表实现,不能存放重复的元素。元素类型必须可以比较是否相等,因为这可以确定元素什么时候相等。

成员函数:

  1. empty():检查容器是否为空。
  2. size():返回容器中的元素数。
  3. insert():插入元素。
  4. clear():清除内容。
  5. count():返回匹配特定键的元素数量。
  6. find():寻找带有特定键的元素。
  7. erase()--删除集合中的元素。

unordered_map

unordered_map是关联容器,含有带唯一键的键-值对。

搜索、插入和元素移除拥有平均常数时间复杂度。

元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。

成员函数:

  1. empty():检查容器是否为空。
  2. size():返回可容纳的元素数。
  3. insert():插入元素。
  4. clear():清除内容。
  5. count():返回匹配特定键的元素数量。
  6. find():寻找带有特定键的元素。
  7. erase()--删除集合中的元素。

6.C/C++中宏定义的相关知识

宏定义可以把一个名称指定成任何一个文本。在完成宏定义后,无论宏名称出现在源代码的何处,预处理器都会将其替换成指定的文本。

//define 宏名 文本
#define WeThinkIn 666688889999

//define 宏名(参数) 文本
#define R(a,b) (a/b)
//注:带参数的宏替换最好在表达式整体上加括号,避免结果受其他运算影响。

宏定义的优点:

  1. 方便程序修改,如果一个常量在程序中大量使用,我们可以使用宏定义为其设置一个标识符。当我们想修改这个常量时,直接修改宏定义处即可,不必在程序中海量寻找所有相关位置。
  2. 提高程序的运行效率,使用带参数的宏定义可以完成函数的功能,但同时又比函数节省系统开销,提升程序运行效率。(无需调用函数这个流程)

宏定义和函数的区别:

  1. 宏在预处理阶段完成替换,之后替换的文本参与编译,相当于是恒等代换过程,运行时不存在函数调用,执行起来更快;而函数调用在运行时需要跳转到具体调用函数。
  2. 宏定义没有返回值;函数调用具有返回值。
  3. 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
  4. 宏定义不是说明或者语句,结尾不用加分号。
  5. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用# undef命令;而函数作用域在函数调用处。

7.C/C++中typedef关键字的相关知识

我们可以使用typedef关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称以及其他类型等名称。

在工业界中,我们一般在如下两个场景中会见到typedef的身影。

// 1.为基本数据类型定义新的类型名
typedef unsigned int WeThinkIn_int;
typedef char* WeThinkIn_point;
  
// 2.为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
typedef struct target_Object
{
    int x;
    int y;
} WeThinkIn_Object;

typedef与宏定义的区别:

  1. 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
  2. 宏替换发生在预处理阶段,属于文本恒等替换;typedef是编译中发挥作用。
  3. 宏定义参数没有类型,不进行类型检查;typedef参数具有类型,需要检查类型。
  4. 宏不是语句,不用在最后加分号;typedef是语句,要加分号标识结束。
  5. 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。

9.i++和++i哪个执行效率高

在C++中,`++i`通常比`i++`执行效率更高。原因在于: - `++i` 是前置递增,直接对变量进行加一操作并返回新值,不需要创建临时对象。 - `i++` 是后置递增,先保存变量的当前值,执行加一操作,然后返回旧值,因此可能需要创建临时对象以存储旧值,这在某些情况下会增加开销。 因此,`++i` 在大多数情况下比 `i++` 更高效。

10.数组名是什么?

在C++中,数组名是一个指向数组第一个元素的常量指针。它具有以下特点: 1. 指向数组的首元素:数组名表示数组在内存中的起始地址,即数组第一个元素的地址。 2. 不可修改:数组名是一个常量指针,它的值(即地址)不能被修改。 3. 隐式转换:在许多表达式中,数组名会隐式转换为指向数组第一个元素的指针。例如,在函数调用中传递数组时,实际上传递的是数组首元素的指针。 示例如下: int arr[5] = {1, 2, 3, 4, 5}; int* ptr = arr; // 数组名 arr 隐式转换为指向 arr[0] 的指针 在这个示例中,`arr` 是数组名,它等同于指向 `arr[0]` 的指针,因此 `ptr` 也指向 `arr[0]`。

11.C++中小数用二进制如何表示

小数在计算机中使用二进制表示时,采用的是浮点数表示法。这种表示法包括定点和浮点两种,但浮点数是更常用的方式。浮点数表示法类似于科学计数法,可以表示很大的范围和精度。

12.C++里有哪些类型转换运算符?

C++里有四种类型转换运算符:`static_cast`用于一般类型转换,`dynamic_cast`用于运行时类型安全的多态类型转换,`const_cast`用于修改对象的常量性,`reinterpret_cast`用于低级别的、几乎不受限制的类型转换。这些运算符提供了比传统C风格类型转换更安全和明确的转换方式。

13.C++中赋值与初始化的区别

赋值是给已存在的对象重新赋值,而初始化是创建对象时赋予其初始值。赋值操作发生在对象已经存在之后,而初始化操作在对象创建时就完成了。初始化通常在对象声明时通过构造函数完成,而赋值则可以在对象生命周期的任何时刻进行。 const成员和引用是如何初始化的 `const`成员和引用必须通过初始化列表在对象构造函数中进行初始化,因为它们在对象创建后无法被赋值或修改。

14.C++中new、delete、malloc、free关系

`new`和`delete`是C++中用于动态内存分配和释放的运算符。`new`运算符在堆上分配内存并调用构造函数来初始化对象,而`delete`运算符则释放内存并调用析构函数来清理对象。它们提供了类型安全的内存管理方式,适合用于C++中的面向对象编程。

mallocfree是C语言中用于动态内存分配和释放的函数。malloc函数分配指定字节的内存块,但不调用构造函数进行初始化,而free函数释放之前使用malloc分配的内存块,但不调用析构函数进行清理。它们不具有类型安全性,因此在C++中通常建议使用newdelete替代。

15.介绍一下register关键字

`register`关键字用于建议编译器将变量存储在寄存器中以提高访问速度,但现代编译器通常会自行优化变量的存储位置,因此这个关键字的实际影响较小且在C++17中已被弃用。

16.介绍一下const关键字

`const`关键字用于声明不可修改的变量、指针或成员函数,确保其值或状态在程序运行过程中保持不变,从而增加代码的安全性和可读性。

17.C++中介绍一下delete与 delete []区别

`delete`用于释放单个对象的内存并调用其析构函数,而`delete[]`用于释放数组对象的内存并调用每个元素的析构函数,使用不当会导致未定义行为和潜在的内存泄漏或崩溃。

18.介绍一下new operator和operator new 的区别

`new` operator是C++中的关键字,用于分配内存并调用对象的构造函数,而`operator new`是一个函数,类似于`malloc`,只负责分配内存,不调用构造函数,允许用户自定义内存分配的行为。

27.介绍一下external关键字]

`extern`关键字用于声明变量或函数在其他文件中定义,以实现跨文件的访问和共享,避免重复定义,通常用于变量的声明而不是定义,确保链接时能够找到正确的符号。

28.介绍一下volatile关键字]

`volatile`关键字用于指示编译器一个变量的值可能会被程序外部因素(如硬件或另一个线程)修改,从而阻止编译器对该变量进行优化,确保每次访问都从内存中读取,以保证程序能够正确处理这些变化。

29.介绍一下dynamic在什么时候使用

`dynamic_cast`在C++中用于运行时类型安全转换,特别是用于在继承层次结构中向下转换指针或引用,确保转换的合法性,并在转换失败时返回`nullptr`(对于指针)或抛出`std::bad_cast`异常(对于引用)。

30.介绍一下vector优缺点

`std::vector`是C++标准库中的动态数组,具有自动管理内存、支持随机访问、高效插入和删除尾部元素的优点,但在中间位置插入或删除元素时性能较差,并且在重新分配内存时可能导致迭代器失效和性能开销。

31.介绍一下list优缺点

`std::list`是C++标准库中的双向链表,具有快速的插入和删除操作,不会导致迭代器失效的优点,但不支持随机访问,且遍历和查找元素的性能相对较差。

32.介绍一下deque优缺点

`std::deque`是C++标准库中的双端队列,支持高效的双端插入和删除操作,允许快速随机访问,适合需要在两端频繁操作的场景,但在中间位置插入和删除元素时性能较差,且与`std::vector`相比,内存使用效率稍低。

33.介绍一下map&set优缺点

`std::map`和`std::set`是C++标准库中的关联容器,提供高效的键值对存储(`map`)和唯一元素存储(`set`)以及自动排序功能,具有O(log n)的查找、插入和删除性能,但由于底层使用红黑树实现,较`unordered_map`和`unordered_set`(基于哈希表)在特定场景下的访问速度稍慢。

34.介绍一下mutable关键字的作用

`mutable`关键字用于允许对象的成员变量在`const`成员函数中被修改,通常用于需要在逻辑上视为常量但在实现上可能需要改变状态的成员变量,例如缓存或统计信息。

42.宏函数和自定义函数的区别

宏函数通过`#define`定义,进行简单的文本替换,没有类型检查和作用域控制,可能导致难以调试的错误;自定义函数则在编译时处理,支持类型检查和作用域控制,提供更安全和可靠的代码执行方式。

43.函数调用的步骤

函数重载要求同一作用域内的多个函数具有相同的名称,但参数列表必须不同,包括参数的数量、类型或顺序,以便编译器能够区分并正确调用相应的函数版本。

48.C++定义常量两种方式是什么?

#define 宏常量: #define 常量名 常量值 通常在文件上方定义,表示一个常量 const修饰的变量 const 数据类型 常量名 = 常量值 通常在变量定义前加关键字const,修饰该变量为常量,不可修改

52.#include与#include"file.h"的区别?

在C/C++中,#include 和 #include "file.h" 都用于包含头文件,但它们有一些重要的区别:
#include <file.h>:
    这种方式主要用于包含标准库头文件。
    编译器会在标准系统目录中查找指定的头文件。
    系统目录通常包括编译器默认的头文件路径以及在编译器配置中指定的路径。

#include "file.h":
    这种方式主要用于包含用户自定义的头文件。
    编译器首先在当前源文件所在的目录中查找指定的头文件。
    如果在当前目录中找不到,编译器会在标准系统目录中继续查找。
    可以用于包含项目中的其他模块或文件。

总体来说: 使用 #include "file.h" 可以确保编译器优先查找当前项目目录,这在开发过程中非常有用,尤其是当你有自定义的头文件时。 使用 #include <file.h> 可以避免与标准库头文件冲突,并且编译器在查找标准库头文件时通常会进行一些优化。

53.介绍一下C/C++各自的特点?

C语言是一种结构化语言,侧重于过程编程,基于算法和数据结构,关注的是如何通过过程或函数从输入得到输出。C++则是一种面向对象的语言,基于类、对象和继承,关注的是如何构建一个对象模型,使其能够与所处理的问题相适应,并通过获取对象的状态信息来实现输出或过程控制。

54.介绍一下const 用途?

在C和C++中,const 关键字用于定义常量和限制变量的可修改性。它的主要用途如下:

  1. 定义常量
    • 使用 const 关键字可以定义不可修改的变量,这样可以避免在程序中意外改变其值。
    const int MAX_SIZE = 100;
  2. 保护函数参数
    • 在函数参数中使用 const 可以防止在函数内部修改传入的参数值,确保参数在函数内部是只读的。
    void printArray(const int* array, int size);
  3. 修饰成员函数
    • 在C++中,const 可以用来修饰成员函数,表示该成员函数不会修改对象的状态。即,该成员函数不允许修改类的成员变量。
    class MyClass {
    public:
        int getValue() const;
    private:
        int value;
    };
  4. 指针和引用
    • const 可以用于修饰指针和引用,分别表示指针指向的内容不可变或指针本身不可变。
    const int* ptr1;   // 指向常量的指针,指针指向的内容不可变
    int* const ptr2;   // 常量指针,指针本身不可变
    const int* const ptr3; // 指向常量的常量指针,指针本身和指针指向的内容都不可变
  5. 常量引用
    • 在C++中,常量引用可以用于函数参数,避免拷贝大的对象,同时防止修改对象。
    void display(const std::string& str);

示例代码

定义常量

const double PI = 3.14159;

函数参数

void displayArray(const int* arr, int size) {
    for (int i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
}

修饰成员函数

class Circle {
public:
    Circle(double r) : radius(r) {}
    double getArea() const {
        return 3.14159 * radius * radius;
    }
private:
    double radius;
};

指针和引用

void example() {
    int value = 10;
    const int* ptr1 = &value;   // ptr1指向的内容不能被改变
    int* const ptr2 = &value;   // ptr2不能指向其他地址
    const int* const ptr3 = &value; // ptr3的指向和内容都不能被改变
}

通过使用 const,可以提高代码的安全性和可读性,防止意外修改数据,确保函数行为的一致性。

55.const和#define有什么区别?

`const` 和 `#define` 在C和C++中都有定义常量的作用,但它们之间有一些重要的区别:
  1. 类型检查

    • const 变量具有类型,编译器会进行类型检查,确保类型安全。
    • #define 定义的宏常量没有类型,编译器不进行类型检查,这可能会导致一些意想不到的错误。
  2. 作用域

    • const 常量具有作用域,可以是局部的(如在函数内定义)或全局的(如在文件顶层定义),并且遵循C/C++的作用域规则。
    • #define 定义的宏常量没有作用域概念,它们在预处理阶段被简单地文本替换,作用于整个文件。
  3. 存储方式

    • const 常量在内存中有实际的存储位置,可以取地址。
    • #define 定义的宏常量在编译后没有存储位置,只是简单的文本替换,不能取地址。
  4. 调试支持

    • const 常量在调试时可以查看其值,因为它们在内存中有实际存储位置。
    • #define 宏常量在调试时难以查看,因为它们只是文本替换,没有实际存储位置。
  5. 语法和使用

    • const 常量需要指定类型,并且遵循变量的声明和初始化规则。
    • #define 宏常量是预处理指令,不需要类型和分号,只需简单的文本替换。

示例

使用 const 定义常量

const int MAX_SIZE = 100;

void example() {
    const double PI = 3.14159;
    // MAX_SIZE 和 PI 都具有类型和作用域,可以进行类型检查和调试
}

使用 #define 定义宏常量

#define MAX_SIZE 100

void example() {
    #define PI 3.14159
    // MAX_SIZE 和 PI 都是简单的文本替换,没有类型检查和作用域
}

详细示例

类型检查

const int MAX_SIZE = 100;
float size = MAX_SIZE; // 类型安全,编译器会检查类型

#define MAX_SIZE 100
float size = MAX_SIZE; // 没有类型检查,编译器不会报错

调试支持

const int MAX_SIZE = 100;

#define MAX_SIZE 100

void example() {
    const int x = MAX_SIZE; // 可以在调试时查看 x 的值

    int y = MAX_SIZE; // 编译后,y = 100,没有实际存储位置,难以调试
}

作用域

void example() {
    const int local_const = 50; // 只在函数内有效
    #define LOCAL_MACRO 50 // 在整个文件内有效
}

取地址

const int MAX_SIZE = 100;
const int* ptr = &MAX_SIZE; // 可以取地址

#define MAX_SIZE 100
// int* ptr = &MAX_SIZE; // 错误,无法取地址

综上所述,const 提供了类型安全、作用域管理和调试支持,而 #define 则是简单的文本替换,适用于定义无需类型检查的常量。

70.main函数执行之前会执行什么?执行之后还能执行代码吗?

(1)全局对象的构造函数会在main函数之前执行; (2)可以,可以用_onexit 注册一个函数,它会在main 之后执行;

如果你需要加入一段在main退出后执行的代码,可以使用atexit()函数,注册一个函数。

75.请讲述堆和栈的区别?

(1)申请方式不同。栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小; (2)栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活; (3)栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片;

79.介绍一下static关键字的作用

static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。

81.完成字符串拷贝可以使用sprintfstrcpy及memcpy 函数,请问这些函数有什么区别。

这些函数的区别在于 实现功能以及操作对象不同。 (1)strcpy 函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能。 (2)sprintf 函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。 (3)memcpy 函数顾名思义就是内存拷贝,实现将一个内存块的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。 对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同: • strcpy 无疑是最合适的选择:效率高且调用方便。 • sprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。 • memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。 对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用 。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。

83.C++函数中值的传递方式有哪几种?

答:三种传递方式为:值传递、指针传递和引用传递。

84.C++里面是不是所有的动作都是main()引起的?

比如全局变量的初始化,就不是由main函数引起的

88.程序的局部变量、全局变量、以及动态申请的数据分别存储在哪里?

程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

92. C++11 的新特性都有哪些?

自动类型推断(auto)。 范围for循环。 Lambda表达式和函数闭包。 右值引用和移动语义。 可变参数模板。 初始化列表。 强类型枚举。 智能指针如std::unique_ptr和std::shared_ptr。 空指针关键字(nullptr)。 线程库支持。 新容器如std::array和std::unordered_map。

94.介绍一下左值和右值引用

左值引用是对可寻址的、可重复使用的对象(左值)的引用。它使用传统的单个&符号。右值引用是对临时对象(右值)的引用,使用双&&符号。右值引用允许实现移动语义和完美转发,它可以将资源从一个(临时的)对象转移到另一个对象,提高效率,避免不必要的复制。

95.介绍一下C++转型操作符?

static_cast:用于基本数据类型转换,以及向上转型(将派生类对象或指针转换为基类表示)。 dynamic_cast:用于类的层次结构中的安全向下转型和运行时类型检查,需要运行时RTTI(Run-Time Type Information)支持。 const_cast:用于移除对象的const或volatile属性。 reinterpret_cast:用于低级转换,重新解释底层位模式,可以将一个指针转换为任何其他类型的指针。

55.sizeof与strlen的区别?

`sizeof` 和 `strlen` 都用于获取数据大小,但它们有不同的用途和工作方式。下面是它们之间的区别:

sizeof 操作符

  1. 用途

    • 用于确定变量、数据类型或对象的大小(以字节为单位)。
  2. 工作方式

    • 在编译时计算出其操作数的大小。
  3. 适用对象

    • 可以用于基本数据类型、数组、结构体、类等。
  4. 返回值类型

    • 返回的是 size_t 类型。
  5. 示例

    int a = 10;
    int arr[10];
    struct MyStruct {
        int x;
        double y;
    };
    
    size_t size_a = sizeof(a);          // 计算int类型变量a的大小,通常是4字节
    size_t size_arr = sizeof(arr);      // 计算数组arr的总大小,10 * sizeof(int)
    size_t size_struct = sizeof(MyStruct); // 计算结构体MyStruct的大小

strlen 函数

  1. 用途

    • 用于计算以 null 结尾的字符串的长度(不包括末尾的 null 字符)。
  2. 工作方式

    • 在运行时遍历字符串直到找到 null 字符('\0'),计算其长度。
  3. 适用对象

    • 仅用于以 null 结尾的字符数组或字符串常量。
  4. 返回值类型

    • 返回的是 size_t 类型。
  5. 示例

    const char* str = "Hello, world!";
    size_t length = strlen(str);  // 计算字符串的长度,不包括末尾的'\0',结果是13

详细示例

使用 sizeof 计算类型和数组大小

#include <iostream>

int main() {
    int num = 42;
    int arr[10];
    char str[] = "Hello";

    std::cout << "Size of int: " << sizeof(int) << std::endl;       // 4(通常)
    std::cout << "Size of num: " << sizeof(num) << std::endl;       // 4(通常)
    std::cout << "Size of arr: " << sizeof(arr) << std::endl;       // 40(10 * 4)
    std::cout << "Size of str: " << sizeof(str) << std::endl;       // 6(包括'\0')
    
    return 0;
}

使用 strlen 计算字符串长度

#include <iostream>
#include <cstring>

int main() {
    const char* str1 = "Hello, world!";
    char str2[] = "Hello";

    std::cout << "Length of str1: " << strlen(str1) << std::endl;   // 13
    std::cout << "Length of str2: " << strlen(str2) << std::endl;   // 5
    
    return 0;
}

总结

  • sizeof 用于计算变量、类型或对象的大小,在编译时确定,适用于任何类型。
  • strlen 用于计算以 null 结尾的字符串长度,在运行时确定,只适用于以 null 结尾的字符数组或字符串常量。