- 1.C/C++中面向对象的相关知识
- 2.C/C++中程序的开发流程?
- 3.C/C++中的new和malloc有什么区别?
- 4.C/C++中面向对象和面向过程的区别?
- 5.C/C++中常用容器功能汇总
- 6.C/C++中宏定义的相关知识
- 7.C/C++中typedef关键字的相关知识
- 8.声明和定义的区别是什么?
- 9.i++和++i哪个执行效率高
- 10.数组名是什么?
- 11.C++中小数用二进制如何表示?
- 12.C++里有哪些类型转换运算符?
- 13.C++中赋值与初始化的区别
- 14.C++中new、delete、malloc、free关系
- 15.介绍一下register关键字
- 16.介绍一下const关键字
- 17.C++中介绍一下delete与 delete []区别
- 18.介绍一下newoperator和operatornew的区别
- 19.介绍一下external关键字
- 20.介绍一下volatile关键字
- 21.介绍一下dynamic在什么时候使用
- 22.介绍一下vector优缺点
- 23.介绍一下list优缺点
- 24.介绍一下deque优缺点
- 25.介绍一下map&set优缺点
- 26.介绍一下mutable关键字的作用
- 27.宏函数和自定义函数的区别
- 28.函数调用的步骤
- 29.C++定义常量两种方式是什么?
- 30.#include<file.h> 与 #include "file.h"的区别?
- 31.介绍一下C/C++各自的特点?
- 32.介绍一下const 用途?
- 33.const和#define有什么区别?
- 34.main函数执行之前会执行什么?执行之后还能执行代码吗?
- 35.堆和栈的区别有什么?
- 36.介绍一下static关键字的作用。
- 37.完成字符串拷贝可以使用 sprintf、strcpy 及 memcpy 函数,请问这些函数有什么区别。
- 38.C++函数中值的传递方式有哪几种?
- 39.C++里面是不是所有的动作都是main()引起的?
- 40.程序的局部变量、全局变量、以及动态申请的数据分别存储在哪里?
- 41. C++11 的新特性都有哪些?
- 42. 介绍一下左值和右值引用
- 43. 介绍一下C++转型操作符?
- 44.sizeof与strlen的区别?
面向对象程序设计(Object-oriented programming,OOP)有三大特征 ——封装、继承、多态。
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 关键字:public, protected, private。不写默认为 private。
-
public 成员:可以被任意实体访问。
-
protected 成员:只允许被子类及本类的成员函数访问。
-
private 成员:只允许被本类的成员函数、友元类或友元函数访问。
继承:基类(父类)——> 派生类(子类)
多态:即多种状态(形态)。简单来说,我们可以将多态定义为消息以多种形式显示的能力。多态是以封装和继承为基础的。
C++ 多态分类及实现:
-
重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
-
子类型多态(Subtype Polymorphism,运行期):虚函数
-
参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
-
强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
开发一个C++程序的过程通常包括编辑、编译、链接、运行和调试等步骤。
编辑:编辑是C++程序开发过程的第一步,它主要包括程序文本的输入和修改。任何一种文本编辑器都可以完成这项工作。当用户完成了C++程序的编辑时,应将输入的程序文本保存为以.cpp为扩展名的文件(保存C++头文件时应以.h为扩展名)。
编译:C++是一种高级程序设计语言,它的语法规则与汇编语言和机器语言相比更接近人类自然语言的习惯。然而,计算机能够“看”懂的唯一语言是汇编语言。因此,当我们要让计算机“看”懂一个C++程序时,就必须使用编译器将这个C++程序“翻译”成汇编语言。编译器所做的工作实际上是一种由高级语言到汇编语言的等价变换。
汇编:将汇编语言翻译成机器语言指令。汇编器对汇编语言进行一系列处理后最终产生的输出结构称为目标代码,它是某种计算机的机器指令(二进制),并且在功能上与源代码完全等价。保存源代码和目标代码的文件分别称为源文件和目标文件( .obj)。
链接:要将汇编器产生的目标代码变成可执行程序还需要最后一个步骤——链接。链接工作是由“链接器”完成的,它将编译后产生的一个或多个目标文件与程序中用到的库文件链接起来,形成一个可以在操作系统中直接运行的可执行程序。(linux中的.o文件)
运行和调试:我们接下来就可以执行程序了。如果出现问题我们可以进行调试debug。
new和malloc主要有以下三方面的区别:
-
malloc和free是标准库函数,支持覆盖;new和delete是运算符,支持重载。
-
malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
-
malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
面向对象(Object Oriented Programming,OOP)编程模型首先抽象出各种对象(各种类),并专注于对象与对象之间的交互,对象涉及的方法和属性都封装在对象内部。
面向对象的编程思想是一种依赖于类和对象概念的编程方式,一个形象的例子是将大象装进冰箱:
- 冰箱是一个对象,大象也是一个对象。
- 冰箱有自己的方法,打开、存储、关闭等;大象也有自己的方法,吃、走路等。
- 冰箱有自己的属性:长、宽、高等;大象也有自己的属性:体重、高度、体积等。
面向过程(Procedure Oriented Programming,POP)编程模型是将问题分解成若干步骤(动作),每个步骤(动作)用一个函数来实现,在使用的时候,将数据传递给这些函数。
面向过程的编程思想通常采用自上而下、顺序执行的方式进行,一个形象的例子依旧是将大象装进冰箱:
- 打开冰箱。
- 把大象装进冰箱。
- 关闭冰箱。
总结来说面向对象和面向过程的区别:
-
安全性角度。面向对象比面向过程安全性更高,面向对象将数据访问隐藏在了类的成员函数中,而且类的成员变量和成员函数都有不同的访问属性;而面向过程并没有办法来隐藏程序数据。
-
程序设计角度。面向过程通常将程序分为一个个的函数;而面向对象编程中通常使用一个个对象,函数通常是对象的一个方法。
-
逻辑过程角度。面向过程通常采用自上而下的方法;而面向对象通常采用自下而上的方法。
-
程序扩展性角度。面向对象编程更容易修改程序,更容易添加新功能。
vector是封装动态数组的顺序容器。
成员函数:
- at():所需元素值的引用。
- front():访问第一个元素(返回引用)。
- back():访问最后一个元素(返回引用)。
- beign():返回指向容器第一个元素的迭代器。
- end():返回指向容器末尾段的迭代器。
- empty():检查容器是否为空。
- size():返回容器中的元素数。
- capacity():返回当前存储空间能够容纳的元素数。
- clear():清除内容。
- insert():插入元素。
- erase():擦除元素。
- push_back():将元素添加到容器末尾。
- pop_back():移除末尾元素。
- *max_element(v.begin(), v.end()):返回数组最大值。
- *min_element(v.begin(), v.end()):返回数组最小值。
queue是容器适配器,他是FIFO(先进先出)的数据结构。
成员函数:
- front():访问第一个元素(返回引用)。
- back():访问最后一个元素(返回引用)。
- empty():检查容器是否为空。
- size():返回容器中的元素数。
- push():向队列尾部插入元素。
- pop():删除首个元素。
deque是有下标顺序容器,它允许在其首尾两段快速插入和删除。
成员函数:
- front():访问第一个元素(返回引用)。
- back():访问最后一个元素(返回引用)。
- beign():返回指向容器第一个元素的迭代器。
- end():返回指向容器末尾段的迭代器。
- empty():检查容器是否为空。
- size():返回容器中的元素数。
- clear(): 清除内容。
- insert():插入元素。
- erase():擦除元素。
- push_back():将元素添加到容器末尾。
- pop_back():移除末尾元素。
- push_front():插入元素到容器起始位置。
- pop_front():移除首元素。
- at():所需元素值的引用。
成员函数:
-
begin()--返回指向第一个元素的迭代器。
-
clear()--清除所有元素。
-
count()--返回某个值元素的个数。
-
empty()--如果集合为空,返回true。
-
end()--返回指向最后一个元素的迭代器。
-
erase()--删除集合中的元素。
-
find()--返回一个指向被查找到元素的迭代器。
-
insert()--在集合中插入元素。
-
size()--集合中元素的数目。
无序集合基于哈希表实现,不能存放重复的元素。元素类型必须可以比较是否相等,因为这可以确定元素什么时候相等。
成员函数:
- empty():检查容器是否为空。
- size():返回容器中的元素数。
- insert():插入元素。
- clear():清除内容。
- count():返回匹配特定键的元素数量。
- find():寻找带有特定键的元素。
- erase()--删除集合中的元素。
unordered_map是关联容器,含有带唯一键的键-值对。
搜索、插入和元素移除拥有平均常数时间复杂度。
元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。
成员函数:
- empty():检查容器是否为空。
- size():返回可容纳的元素数。
- insert():插入元素。
- clear():清除内容。
- count():返回匹配特定键的元素数量。
- find():寻找带有特定键的元素。
- erase()--删除集合中的元素。
宏定义可以把一个名称指定成任何一个文本。在完成宏定义后,无论宏名称出现在源代码的何处,预处理器都会将其替换成指定的文本。
//define 宏名 文本
#define WeThinkIn 666688889999
//define 宏名(参数) 文本
#define R(a,b) (a/b)
//注:带参数的宏替换最好在表达式整体上加括号,避免结果受其他运算影响。
宏定义的优点:
- 方便程序修改,如果一个常量在程序中大量使用,我们可以使用宏定义为其设置一个标识符。当我们想修改这个常量时,直接修改宏定义处即可,不必在程序中海量寻找所有相关位置。
- 提高程序的运行效率,使用带参数的宏定义可以完成函数的功能,但同时又比函数节省系统开销,提升程序运行效率。(无需调用函数这个流程)
宏定义和函数的区别:
- 宏在预处理阶段完成替换,之后替换的文本参与编译,相当于是恒等代换过程,运行时不存在函数调用,执行起来更快;而函数调用在运行时需要跳转到具体调用函数。
- 宏定义没有返回值;函数调用具有返回值。
- 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
- 宏定义不是说明或者语句,结尾不用加分号。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用# undef命令;而函数作用域在函数调用处。
我们可以使用typedef关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称以及其他类型等名称。
在工业界中,我们一般在如下两个场景中会见到typedef的身影。
// 1.为基本数据类型定义新的类型名
typedef unsigned int WeThinkIn_int;
typedef char* WeThinkIn_point;
// 2.为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
typedef struct target_Object
{
int x;
int y;
} WeThinkIn_Object;
typedef与宏定义的区别:
- 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
- 宏替换发生在预处理阶段,属于文本恒等替换;typedef是编译中发挥作用。
- 宏定义参数没有类型,不进行类型检查;typedef参数具有类型,需要检查类型。
- 宏不是语句,不用在最后加分号;typedef是语句,要加分号标识结束。
- 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。
malloc和free是C语言中用于动态内存分配和释放的函数。malloc函数分配指定字节的内存块,但不调用构造函数进行初始化,而free函数释放之前使用malloc分配的内存块,但不调用析构函数进行清理。它们不具有类型安全性,因此在C++中通常建议使用new和delete替代。
#include <file.h>:
这种方式主要用于包含标准库头文件。
编译器会在标准系统目录中查找指定的头文件。
系统目录通常包括编译器默认的头文件路径以及在编译器配置中指定的路径。
#include "file.h":
这种方式主要用于包含用户自定义的头文件。
编译器首先在当前源文件所在的目录中查找指定的头文件。
如果在当前目录中找不到,编译器会在标准系统目录中继续查找。
可以用于包含项目中的其他模块或文件。
总体来说: 使用 #include "file.h" 可以确保编译器优先查找当前项目目录,这在开发过程中非常有用,尤其是当你有自定义的头文件时。 使用 #include <file.h> 可以避免与标准库头文件冲突,并且编译器在查找标准库头文件时通常会进行一些优化。
C语言是一种结构化语言,侧重于过程编程,基于算法和数据结构,关注的是如何通过过程或函数从输入得到输出。C++则是一种面向对象的语言,基于类、对象和继承,关注的是如何构建一个对象模型,使其能够与所处理的问题相适应,并通过获取对象的状态信息来实现输出或过程控制。在C和C++中,const 关键字用于定义常量和限制变量的可修改性。它的主要用途如下:
- 定义常量:
- 使用
const关键字可以定义不可修改的变量,这样可以避免在程序中意外改变其值。
const int MAX_SIZE = 100;
- 使用
- 保护函数参数:
- 在函数参数中使用
const可以防止在函数内部修改传入的参数值,确保参数在函数内部是只读的。
void printArray(const int* array, int size);
- 在函数参数中使用
- 修饰成员函数:
- 在C++中,
const可以用来修饰成员函数,表示该成员函数不会修改对象的状态。即,该成员函数不允许修改类的成员变量。
class MyClass { public: int getValue() const; private: int value; };
- 在C++中,
- 指针和引用:
const可以用于修饰指针和引用,分别表示指针指向的内容不可变或指针本身不可变。
const int* ptr1; // 指向常量的指针,指针指向的内容不可变 int* const ptr2; // 常量指针,指针本身不可变 const int* const ptr3; // 指向常量的常量指针,指针本身和指针指向的内容都不可变
- 常量引用:
- 在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,可以提高代码的安全性和可读性,防止意外修改数据,确保函数行为的一致性。
-
类型检查:
const变量具有类型,编译器会进行类型检查,确保类型安全。#define定义的宏常量没有类型,编译器不进行类型检查,这可能会导致一些意想不到的错误。
-
作用域:
const常量具有作用域,可以是局部的(如在函数内定义)或全局的(如在文件顶层定义),并且遵循C/C++的作用域规则。#define定义的宏常量没有作用域概念,它们在预处理阶段被简单地文本替换,作用于整个文件。
-
存储方式:
const常量在内存中有实际的存储位置,可以取地址。#define定义的宏常量在编译后没有存储位置,只是简单的文本替换,不能取地址。
-
调试支持:
const常量在调试时可以查看其值,因为它们在内存中有实际存储位置。#define宏常量在调试时难以查看,因为它们只是文本替换,没有实际存储位置。
-
语法和使用:
const常量需要指定类型,并且遵循变量的声明和初始化规则。#define宏常量是预处理指令,不需要类型和分号,只需简单的文本替换。
const int MAX_SIZE = 100;
void example() {
const double PI = 3.14159;
// MAX_SIZE 和 PI 都具有类型和作用域,可以进行类型检查和调试
}#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 则是简单的文本替换,适用于定义无需类型检查的常量。
如果你需要加入一段在main退出后执行的代码,可以使用atexit()函数,注册一个函数。
(1)申请方式不同。栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小; (2)栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活; (3)栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片; static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。 这些函数的区别在于 实现功能以及操作对象不同。 (1)strcpy 函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能。 (2)sprintf 函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。 (3)memcpy 函数顾名思义就是内存拷贝,实现将一个内存块的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。 对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同: • strcpy 无疑是最合适的选择:效率高且调用方便。 • sprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。 • memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。 对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用 。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。 答:三种传递方式为:值传递、指针传递和引用传递。 比如全局变量的初始化,就不是由main函数引起的 程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。 自动类型推断(auto)。 范围for循环。 Lambda表达式和函数闭包。 右值引用和移动语义。 可变参数模板。 初始化列表。 强类型枚举。 智能指针如std::unique_ptr和std::shared_ptr。 空指针关键字(nullptr)。 线程库支持。 新容器如std::array和std::unordered_map。 左值引用是对可寻址的、可重复使用的对象(左值)的引用。它使用传统的单个&符号。右值引用是对临时对象(右值)的引用,使用双&&符号。右值引用允许实现移动语义和完美转发,它可以将资源从一个(临时的)对象转移到另一个对象,提高效率,避免不必要的复制。 static_cast:用于基本数据类型转换,以及向上转型(将派生类对象或指针转换为基类表示)。 dynamic_cast:用于类的层次结构中的安全向下转型和运行时类型检查,需要运行时RTTI(Run-Time Type Information)支持。 const_cast:用于移除对象的const或volatile属性。 reinterpret_cast:用于低级转换,重新解释底层位模式,可以将一个指针转换为任何其他类型的指针。 `sizeof` 和 `strlen` 都用于获取数据大小,但它们有不同的用途和工作方式。下面是它们之间的区别:-
用途:
- 用于确定变量、数据类型或对象的大小(以字节为单位)。
-
工作方式:
- 在编译时计算出其操作数的大小。
-
适用对象:
- 可以用于基本数据类型、数组、结构体、类等。
-
返回值类型:
- 返回的是
size_t类型。
- 返回的是
-
示例:
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的大小
-
用途:
- 用于计算以
null结尾的字符串的长度(不包括末尾的null字符)。
- 用于计算以
-
工作方式:
- 在运行时遍历字符串直到找到
null字符('\0'),计算其长度。
- 在运行时遍历字符串直到找到
-
适用对象:
- 仅用于以
null结尾的字符数组或字符串常量。
- 仅用于以
-
返回值类型:
- 返回的是
size_t类型。
- 返回的是
-
示例:
const char* str = "Hello, world!"; size_t length = strlen(str); // 计算字符串的长度,不包括末尾的'\0',结果是13
#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;
}#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结尾的字符数组或字符串常量。