gcc和g++的区别
简单来说,gcc和g++都是GNU(组织)的编译器。 需要注意以下几点:
gcc和g++都可以编译c代码和c++代码。 但是:如果后缀是.c,gcc会将其视为C程序,g++会将其视为C++程序; 如果后缀是.cpp,则两者都会将其视为C++程序。
在编译阶段,g++会调用gcc。 对于c++代码来说,两者是等价的。 但由于gcc命令无法自动连接C++程序所使用的库,因此通常使用g++来完成链接。
您可以使用 gcc/g++ 进行编译,使用 g++ 或 gcc -lstdc++ 进行链接。 由于gcc命令无法自动与C++程序使用的库进行连接(当然你可以选择手动链接,使用命令如下),所以通常使用g++来完成连接。 但在编译阶段,g++会自动调用gcc,两者是等价的。
gcc main.cpp -lstdc++
gcc编译的四步,以最简单的hello.c为例
第一步:gcc hello.c
该命令是隐式执行的
(1)预处理
(2)编译
(3)编译
(4) 链接
这里没有指定输出文件,默认输出是a.out
gcc编译C源代码有四个步骤:
预处理---->编译---->组装---->链接
下面我们就利用gcc命令选项来一一分析gcc的流程。
1)预处理(Pre-)
在此阶段,编译器会在 C 源代码中添加包含的头文件,例如 stdio.h。
参数:“-E”
用法:gcc -E hello.c -o hello.i
功能:预处理hello.c并输出hello.i文件。
2)编译()
第二步是编译阶段。 在这个阶段,gcc首先检查代码的规范性,是否存在语法错误等,以确定代码的实际工作情况。 检查正确后,gcc 将代码翻译成汇编。 语言。
参数”
用法:gcc –S hello.i –o hello.s
功能:将预处理输出文件hello.i组装到hello.s文件中。
3)组装()
汇编阶段将编译阶段生成的“.s”文件转换为二进制目标代码“.o”文件
参数:“-c”
用法:gcc –c hello.s –o hello.o
功能:编译汇编输出文件hello.s,输出hello.o文件。
4) 链接
编译成功后,进入链接阶段。
用法:gcc hello.o –o hello
功能:将编译后的输出文件hello.o链接成最终的可执行文件hello.o。
当您运行可执行文件时,正确的结果如下所示。
>>> ./你好
你好世界!
C++11 包含大量新功能:包括表达式、类型推断:auto、以及对模板的大量改进。
它实际上有点像 auto 的反函数,它允许你声明一个变量。并且你可以从变量或表达式中获取类型
它是为了解决C++中NULL原有的歧义问题而引入的新类型。 由于NULL实际上代表0,
简化的for循环,可用于遍历begin和end函数定义的数组、容器和序列(即有),for (auto p: m)
表达式可用于创建和定义匿名函数对象以简化编程。 语法如下:[函数对象参数](运算符重载函数参数)->返回值类型{函数体}
vector iv{5, 4, 3, 2, 1};
int a = 2, b = 1;
for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<
for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);}); // (2)
for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3)
可变长度参数模板。 C++11中引入了变长参数模板,因此发明了一种新的数据类型:元组。 Tuple是一个N元组。能够传入1个、2个甚至多个不同类型的数据
auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});
这避免了在之前的对中嵌套对的丑陋做法。使代码更清晰
更优雅的初始化方法,在C++11引入之前。 只有数组可以使用初始化列表。 如果其他容器想要使用初始化列表,只能使用以下方法:
int arr[3] = {1, 2, 3}
vector v(arr, arr + 3);
在C++11中,我们可以使用以下语法来执行替换:
int arr[3]{1, 2, 3};
vector iv{1, 2, 3};
map{{1, "a"}, {2, "b"}};
string str{"Hello World"};
什么是智能指针?智能指针的原理
将基本类型指针封装成类对象指针(这个类必须是模板,以适应不同基本类型的需要),并在析构函数中编写语句删除指针指向的内存空间。
智能指针是一个类。 在该类的构造函数中传递一个普通的指针,在析构函数中释放传递的指针。 智能指针类是堆栈上的对象,因此当函数(或程序)结束时它们会自动释放。
智能指针是在堆栈上创建的对象。 当函数退出时,它的析构函数将被调用。 这个析构函数往往包含一堆计数等条件判断。 如果达到一定条件,真实指针指向的空间就会被释放。
防范措施:
不能直接将指针赋给智能指针,一个是类,一个是指针。
常用的智能指针
C++11之后提供了智能指针,包含在头文件中,,,
1)std::,存在很多问题。 不支持复制(复制构造函数)和赋值(=),但是复制或者赋值的时候不会提示错误。所以可能会导致程序崩溃,比如
auto_ptr p1(new string ("auto") ; //#1
auto_ptr p2; //#2
p2 = p1; //#3
在语句 #3 中,p2 接管对象的所有权后,p1 的所有权将被剥夺。 如前所述,这是一件好事,因为它可以防止 p1 和 p2 的析构函数尝试删除同一个对象;
但如果程序随后尝试使用 p1,这将是一件坏事,因为 p1 不再指向有效数据。 如果再次访问p1指向的内容,程序就会崩溃。
它是C++98提供的解决方案。 C+11 已经放弃了它。 放弃的原因可以用一句话来概括:避免潜在的内存损坏问题。
2)C++11中引入,不支持复制和赋值,但直接赋值会导致编译错误。 如果你确实想赋值,则需要使用:std::move。 例如:
std::unique_ptr p1(new int(5)) // #4
std::unique_ptr p2 = p1; // 编译会出错 //#5
std::unique_ptr p3 = std::move(p1); // 转移所有权, 现在那块内存归p3所有, p1成为无效的指针. //#6
编译器认为语句 #5 非法,因此比语句更安全
但它还有更聪明的地方。 有时,将一个智能指针分配给另一个智能指针不会留下危险的悬空指针。当程序尝试将一个值分配给另一个值时,如果源是临时右值,则编译器会允许这样做;否则,编译器会允许这样做。 如果源将存在一段时间,它将禁用它。
unique_ptr pu1(new string ("hello world"));
unique_ptr pu2;
pu2 = pu1; // #1 not allowed
unique_ptr pu3;
pu3 = unique_ptr(new string ("You")); // #2 allowed
其中 #1 留下一个可能造成伤害的悬空 (pu1)。 #2 不会留下任何悬空的东西,因为它调用一个构造函数来创建一个临时对象,该对象在其所有权转移到 pu3 后将被销毁。 这种情境行为比允许同时进行任务要好。
3) C++11 或 boost、基于引用计数的智能指针。 可以随意赋值,直到内存的引用计数达到0为止,内存就会被释放。
4)C++11或boost,弱引用。 引用计数的一个问题是相互引用形成一个环,从而导致两个指针指向的内存无法释放。 需要手动打破循环引用或者使用。 顾名思义,就是弱引用,只被引用,不被统计。 如果一块内存同时被 和 引用,当全部被析构时,无论该内存是否还被引用,该内存都会被释放。 因此,不能保证它指向的内存是有效的。 使用前需要检查是否为空指针。
智能指针的作用
C++编程中堆内存的使用是一个非常频繁的操作,堆内存的申请和释放都是由程序员自己管理的。 程序员可以通过自己管理堆内存来提高程序效率,但整体堆内存管理比较麻烦。 C++11引入了智能指针的概念来方便堆内存管理。 使用普通指针很容易造成堆内存泄漏(忘记释放)、二次释放、野指针、程序发生异常时内存泄漏等,使用智能指针可以更好地管理堆内存。
1.C和C++的区别
1)C是面向过程的语言,是一种结构化语言,考虑如何通过过程处理输入以获得输出; C++是一种面向对象的语言,其主要特点是“封装、继承和多态性”。 封装隐藏了实现细节,使代码模块化; 派生类可以继承父类的数据和方法,扩展现有模块,实现代码复用; 多态是“一个接口,多个实现”,通过派生类重写父类的虚函数来实现接口的复用。
2)C和C++动态管理内存的方式不同。 C 使用 /free,而 C++ 也有 new/ 关键字。
3)C++支持函数重载,但C不支持函数重载
4)C++中有引用,但C中不存在引用的概念
2.C++中指针和引用的区别
1) 指针是一个新变量,它存储另一个变量的地址。 我们可以通过访问这个地址来修改另一个变量;
引用只是一个别名还是变量本身? 对引用的任何操作都是对变量本身的操作,以达到修改变量的目的。
2) 引用只有一层,但指针可以有多层。
3)当指针作为参数传递时,仍然是按值传递。 指针本身的值不能被修改。 所指向的对象需要取消引用才能进行操作。
通过引用传递参数时,传入的是变量本身,因此可以修改变量。
3.结构体和联合体(union)的区别
结构:将不同类型的数据组合成一个整体,是自定义类型
社区:几个不同类型的变量一起占用一段内存
1)结构体中的每个成员都有自己独立的地址,并且它们同时存在;
社区的所有成员都占用相同的内存,并且他们不能同时存在;
2)()是内存对齐后所有成员的长度之和,(union)是内存对齐后最长数据成员的长度,
为什么结构需要内存对齐?
1、平台原因(移植原因):并不是所有的硬件平台都可以访问任意地址的任意数据。 有些硬件平台只能在某些地址获取某些类型的数据,否则会抛出硬件异常。
2、硬件原因:内存对齐后,CPU的内存访问速度大大提高。
4.#和const的区别
1) #定义的常量没有类型,给出的是立即数; const 定义的常量有一个类型名并存储在静态区域中
2)加工阶段不同。 # 定义的宏变量在预处理期间被替换,并且可能有多个副本。 const 定义的变量的值在编译时就确定了,并且只有一份副本。
3) #定义的常量不能用指针指向,而const定义的常量可以用指针指向常量的地址。
4) #可以定义简单的函数,但const不能定义函数。
5.重载、覆盖(重写)、隐藏(重定义),三者的区别
1)使用相同的名称来表示多个语义相似,但参数列表不同(参数的类型、数量和顺序不同)的函数。 这就是函数重载,返回值类型可以不同。
特点:作用域相同(同一个类中)、函数名相同、参数不同、关键字可选
2)、派生类覆盖基类的虚函数实现接口的复用,且返回值类型必须相同
特点:作用域不同(基类和派生类),函数名相同,参数相同,关键字必须在基类中(必须是虚函数)
3)、派生类阻塞其基类同名函数,返回值类型可以不同
特征:作用域不同(基类和派生类)、函数名相同、参数不同、或参数相同但没有关键字
6.new、、、free之间的关系
new/,/free都是动态分配内存的方式。
1)严格指定打开空间的大小,而new只需要对象名称
2)当new为对象分配空间时,它会调用该对象的构造函数并调用该对象的析构函数。
既然有了/free,为什么C++中还需要new/呢?
运算符是语言本身的特征,具有固定的语义。 编译器知道它们的含义,编译器解释语义并生成相应的代码。
库函数在一定程度上是库相关的和语言无关的。 编译器不关心库函数的功能。 它只是保证编译、调用函数参数和返回值符合语法,并生成调用函数的代码。
/free 是一个库函数,new/ 是一个 C++ 运算符。 对于非内部数据类型,单独使用/free无法满足动态对象的要求。 new/是运算符,编译器确保调用构造函数和析构函数来初始化/析构对象。 但库函数/free是库函数,不会进行构造/销毁。
7.【】的区别
析构函数只会被调用一次,而[]会调用每个成员的析构函数
用new分配的内存被释放,用new[]分配的内存用[]释放。
多态、虚函数、纯虚函数
多态性:不同的对象接收相同的消息,产生不同的动作。多态性包括编译时多态性和运行时多态性
运行时多态性是通过继承和虚函数来体现的。
编译时多态性:运算符重载。
封装可以隐藏实现细节,使代码模块化; 继承可以扩展现有的代码模块(类); 他们的目的是重用代码。 多态还具有代码复用的功能,解决了项目中的紧耦合问题,提高了程序的可扩展性。 C++中实现多态性的机制非常简单。 在继承体系下,父类的某个函数被赋予为虚函数(即添加关键字),并在派生类中重写该虚函数,使用父类的指针。 或者通过引用调用虚函数。 通过基类指针或指向派生类的引用来访问派生类中被重写的同名成员函数。 对于虚函数调用,每个对象内部都有一个虚表指针。 构造子类对象时,会执行构造函数创建虚表并初始化虚表指针。 虚拟表指针被初始化到该类。 虚拟表示。 所以在程序中,无论你的对象类型如何转换,对象内部的虚表指针都是固定的。 因此,可以实现动态对象函数调用。 这就是C++多态实现的原理。
需要注意的几点总结(基类有虚函数):
1.每个类都有一个虚拟表。 单继承的子类有一个虚表,子类对象有一个虚表指针。 如果子类有多重继承(同时继承多个基类),则子类维护多个虚函数表(为不同的基类建立不同的虚表),则该子类的对象也会包含多个虚表指针。
2.虚表可以继承。 如果子类没有重写虚函数,子类的虚表中仍然会有该函数的地址,但这个地址指向基类的虚函数实现。 如果基类有3个虚函数,那么基类的虚函数表中就会有3项(虚函数地址)。 派生类还将有一个虚拟表,其中至少包含三个项目。 如果对应的虚函数被重写,那么虚表就会有三项。 地址将发生变化并指向其自己的虚拟函数实现。 如果派生类有自己的虚函数,则该条目将被添加到虚函数表中。
3、派生类的虚表中的虚函数地址的顺序与基类的虚表中的虚函数地址的顺序相同。
第一:当编译器发现类中存在虚函数时,它会自动为每个包含虚函数的类生成一个虚函数表,也称为虚表。 表是一维数组,虚表存储虚函数。 函数的入口地址。
第二:编译器会在每个对象的前四个字节中保存一个虚表指针,即(vptr),指向该对象所属类的虚表。 在程序运行的适当时候,根据对象的类型初始化vptr,使vptr指向正确的虚表,这样当调用虚函数时,就能找到正确的函数。
第三:所谓适时,当派生类定义一个对象时,程序会自动调用构造函数,在构造函数中创建虚拟表,并初始化虚拟表。 构造子类对象时,会先调用父类的构造函数。 此时,编译器只“看到”父类,并为父类对象初始化虚表指针,使其指向父类的虚表; 当调用构造子类时,初始化子类对象的虚表指针,使其指向子类的虚表。
虚函数:基类中使用的成员函数。 允许在派生类中重新定义基类的虚函数。
基类的虚函数可以有函数体,基类也可以被实例化。
虚函数必须有函数体,否则编译无法通过。
虚函数不需要在子类中重写。
构造函数不能是虚函数。
纯虚函数:在基类中为其派生类保留一个名称,以便派生类可以根据需要进行定义。
包含纯虚函数的类是抽象类。
纯虚函数后面跟=0;
抽象类不能被实例化。 但可以定义指针。
如果派生类没有基类的纯虚函数,那么它仍然是抽象类。
抽象类可以包含虚函数。
8.你用过STL库吗? 常见的STL容器有哪些? 你用过多少种算法?
STL由两部分组成:容器和算法
容器是存储数据的地方,例如数组,分为顺序容器和关联容器两类。
顺序容器,其中的元素不一定按顺序排列,但可以排序,如list、queue、stack、heap、-queue、slist
关联容器的内部结构是平衡二叉树。 每个元素都有一个键值和一个实际值,例如map、set、、
算法包括排序、复制等,以及每个容器特有的算法。
迭代器是STL的精髓。 迭代器提供了一种按顺序访问容器中包含的元素的方法,而无需暴露容器的内部结构。 它将容器和算法分开,使它们相互独立。 设计。
9.你认识const吗?解释一下它的作用
const 修饰类的成员变量,表示常量不能修改
类的 const 修饰成员函数意味着该函数不会修改类中的数据成员,也不会调用其他非 const 成员函数。
const函数只能调用const函数,非const函数可以调用const函数
10. 虚拟功能是如何实现的?
每一个包含虚函数的类都至少有一个对应的虚函数表,其中存储了该类的所有虚函数对应的函数指针(地址)。
类的示例对象不包含虚函数表,仅包含虚指针;
派生类将生成与基类兼容的虚函数表。
11.堆和栈的区别
1)栈存放函数参数值和局部变量,由编译器自动分配和释放。
堆是new分配的内存块。 它由应用程序控制,需要程序员手动释放。 如果没有,操作系统会在程序结束后自动回收它。
2)因为堆分配需要频繁使用new/,所以内存空间会不连续,会出现很多碎片。
3)堆的增长空间向上,地址更大。 栈的增长空间向下,地址更小。
12.关键词的作用
1)函数体内:被修改的局部变量的作用域是函数体内。 与 auto 变量不同,它的内存只分配一次,因此它的值在下次调用时保持最后的值。
2)模块内:修改后的全局变量或全局函数可以被模块内的所有函数访问,但不能被模块外的其他函数访问。 使用范围仅限于声明它的模块。
3)在类中:修改成员变量,表明该变量属于整个类,类中所有对象只有一份。
4)在类中:修改成员函数,表明该函数属于整个类。 它不接受this指针,只能访问类中的成员变量。
注意与const的区别! ! ! const强调值不可修改,但强调唯一副本,对于所有类的对象
13、STL中map和set的原理(关联容器)
Map和Set的底层实现主要是通过红黑树来实现的。
红黑树是一种特殊的二叉搜索树
1)每个节点要么是黑色,要么是红色
2)根节点为黑色
3)每个叶节点(NIL)都是黑色的。 【注:这里的叶子节点是指为空(NIL或NULL)的叶子节点! ]
4)如果一个节点是红色的,那么它的子节点一定是黑色的
5) 从一个节点到该节点的后代节点的所有路径都包含相同数量的黑色节点。
特征4)5)决定了没有一条路径会比其他路径长一倍,因此红黑树是一棵近乎平衡的二叉树。
14.##“file.h”的区别
前者是从标准库路径中找到的
后者来自当前的工作路径
15.什么是内存泄漏? 面对内存泄漏和指针越界你有什么方法呢?
动态分配内存开辟的空间在使用后没有手动释放,导致内存一直被占用,这就是内存泄漏。
方法:/free必须匹配。 分配指针时,要注意分配的指针是否需要释放; 使用时要记住指针的长度,防止越界。