【春招预热】C++回炉重造
2022/3/20 14:27:57
本文主要是介绍【春招预热】C++回炉重造,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文主要参照[https://m.nowcoder.com/tutorial/93/a34ed23d58b84da3a707c70371f59c21]进行梳理,进行一定补充,更正了一些错误,删除部分失效内容。
C++基础知识
C and C++
导入C函数的关键字 - extern "C"
指示编译器这一段代码将按照c来编译
编译区别:C++编译时会包含参数类型,而C不支持重载,因此编译后代码不会包含函数类型,而仅有函数名
extern "C" int strcmp(const char* c1, const char* c2);
static变量初始化
C:初始化发生在编译阶段
C++:首次使用时在进行构造
static
保存在静态存储区。多次访问函数会得到之前的值
全局静态变量作用在全局域和文件域,程序结束后会后内存。
全局变量作用在全局域,分配在静态数据区。
局部变脸作用在局部作用域,分配在栈上,出了生存周期就会回收内存。
内联函数和宏函数inline define
宏函数在预编译阶段做代码替换,不检查参数类型,本质上并不是函数
内联函数在编译阶段做代码插入,检查返回类型和返回值,本质上是函数
inline 普通函数在调用时需要寻址,inline可以减少这个开销。inline不允许调用自己,不允许循环和switch,否则编译时当作一般函数
const define
const是常量,单独存放在常量内存区,define不需要存放的空间
define在预编译阶段替换,const在编译阶段生效
const有类型,define没有
const int a;// 常量,a不变 const int* a;// a指向的地址的值不变。*a int const* a;//同上 int *const a;//a指向的地址不变,a不变 const int *const a;//*a不变,a也不变
i++和++i
先赋值,后增加
++i效率更高,i++不能做左值
new和malloc
new是运算符,可以重载,会调用构造函数,分配失败抛出异常
malloc是c库的函数,如果分配失败返回null,返回的是指针,需要强制类型转换,需要指定空间大小
(???https://m.nowcoder.com/tutorial/93/a34ed23d58b84da3a707c70371f59c21)malloc采用内存池的管理方式,减少内存碎片。
野指针
指针指向的位置是不可知的。释放内存后不及时置空,依然指向该内存。可能出现非法访问。
char *p = (char*)malloc(sizeof(char)*100); strcpy(p, "1234"); free(p);//内存被释放,但指针依然指向原本的地址 //assert(p != NULL); if(p!=NULL){//判断失效,没有预防 strcpy(p,"1234"); }
ptr为nullptr时,可以调用成员函数吗?可以。因为编译时对象绑定了函数地址,但是涉及到this时会运行错误。
fish* pFish = nullptr; pFish->print();//ok pFish->add1();//this = nullptr,运行出错
函数指针
是指向函数的指针变量,函数指针的值即为函数入口地址
可以用于回调,如sort()函数,允许传入自定义的比较函数,这里使用的就是函数指针。回调:我们可以调用别人的API,而别人的库中调用我写的函数即为回调。
引用传递和值传递
值传递,形参是拷贝,对形参的改变不影响原来的变量实参
引用传递,传递的是原变量的别名,对形参的更改就是对实参的更改。形参作为局部变量在栈上有空间,但是存的是实参的地址。对形参的操作会通过间接寻址访问主调函数中的实参变量。
C++内存
堆栈
- 堆是数组结构,栈是栈结构
- 栈由系统分配,保存局部变量,参数,堆一般由程序员来分配释放
- 栈一级缓存,堆二级缓存,栈比较快
简述C++内存管理
内存分配方式
C++内存分为五个区,堆,栈,局部/静态区,自由存储区,常量区
自由存储区:malloc分配,free释放
内存 -> 分配 -> 初始化 (避免越界)------->释放
对应:未分配使用,分配未初始化,越界,忘记释放,释放了继续用
NULL,下标不越界,申请释放配对,防止内存泄漏,free后NUL防止野指针,使用只能指针
内存泄漏
- malloc不free,new不delete
- 子类继承父类,父类析构是虚函数(???)
- Windows句柄没有释放(???)
程序section/内存模型
从高地址到低地址:kernel,环境变量,命令行参数,栈,共享空间,堆,.bss,.data,.text。受保护的地址
.bss 运行前清零,保存未初始化和初始化为0的一块内存区域
.data 初始化的全局变量,程序会进行初始化
.text 代码段,只读
字节对齐
struct,union,class需要对齐,size是是最宽变量的整数倍不足要补齐,struct中struct,子struct要从最宽的开始放
保证存取效率,如果不齐的话一个变量要读多次,组合成需要的数据
程序启动过程
https://m.nowcoder.com/tutorial/93/8f38bec08f974de192275e5366d8ae24
进程分配,虚拟内存映射
导入符号表,动态链接库
初始化全局变量
进入程序入口函数
面向对象
多态
静态(编译时)多态
在编译阶段即可确定下来,主要通过重载:函数重载、运算符重载
动态(运行时)多态
程序运行时才可确定
继承、虚函数。公有继承,将后代类对象赋值给祖先类
动态联编:目标对象的类型在运行时确定,只有采用指向基类对象的指针或者引用来第调用虚函数时,才会按动态联编的方式调用
重写 重载
子类可以重新定义父类中已经存在的函数,返回类型,参数列表,函数名均一致,父类中被重写的函数由virtual修饰。
不同的参数的同名函数
实现
- 重载:命名倾轧计数,在编译阶段完成,加上参数类型的首字母用于区分
- 重写:根据对象的实际类型来调用不同类的函数,使用虚函数表
虚函数
虚函数:virtual说明,在派生类中重新定义
virtual <int><testf>(){}
纯虚函数:基类中只声明,没有具体实现,派生类中必须重定义该函数
virtual <int><testf>() = 0;
虚函数表 类对象A的指针包含一个指向该类虚函数表的指针,而虚函数表指向该类的虚函数,因此每个类的对象都会调用自己类的虚函数
静态成员函数、内联函数、友元函数和构造函数不能被说明为虚函数,析构函数可以
为什么
- 不行。虚函数需要虚表,而虚表存储在对象的空间中,调用构造函数前,对象没有实例化,还没有虚表
- 没有意义。构造函数在对象创建时被调用,并不会存在一个父类指针调用子类构造函数的情况
- 可以保证释放基类指针时释放子类空间,不会内存泄漏
深拷贝和浅拷贝
浅拷贝,只赋值值,两个对象可能指向同一块内存,只是不同的别名
深拷贝,申请相同的空间,再赋值,这样就是两块不同的地址,之间的值相同。
移动构造函数
转移所有权,原对象将丢失其内容。当使用一个无名对象来对一个新对象构造初始化时,移动拷贝构造被调用。
struct testmove{ int* p; testmove(int x){ p = new int; *p = x; } testmove(const testmove& copy){ p = new int; *p = *copy.p; } testmove(testmove&& right):p(right.p){ cout<<"move construct"<<endl; right.p = nullptr; } testmove add(testmove x){ *x.p = *x.p+1; return x; } }; int main() { testmove t5(0); cout<<(*t5.p)<<endl; testmove t6(t5.add(t5));//add返回右值,移动构造 cout<<(*t6.p)<<endl; testmove t7(move(t5));//move变为右值,移动构造 cout<<(*t7.p)<<endl; } /* output: 0 move construct 1 move construct 0 */
含有引用成员
需要提供引用成员的构造函数,且需要用初始化列表来初始化
struct testref{ int &r; testref(int &a):r(a){ } }; int main() { int b = 5; int &a = b; testref r(a); cout<<r.r<<endl; }
常函数
在函数名后面加const,表示它不会对(非静态的)数据成员作修改
struct testconst { int a; static int x; void add(int num) const { //a += num;//error: 表达式必须是可修改的左值 x += num; } void sub(int num){ x -= num; } }; int testconst::x = 0; int main() { testconst t; t.add(3); cout<<testconst::x<<endl; const testconst t2{2}; t2.add(3);//常变量可以调用常函数 //t2.sub(3);//error 对象含有与成员 函数 "testconst::sub" 不兼容的类型限定符 cout<<testconst::x<<endl; }
虚继承
- 多重继承时,变量会拷贝。\(A \leftarrow B, A \leftarrow C, B C \leftarrow D\)
- 解决二义性。
实现 virtual base pointer 虚基类指针, 4字节,指向虚基类表,记录子类和虚基类的偏移,这样就找到了虚基类成员。
#include <iostream> using namespace std; class A //大小为4 { public: int a; }; class B : virtual public A //大小为16,变量a,b共8字节,虚基类表指针8 { public: int b; }; class C : virtual public A //与B一样16 { public: int c; virtual void add() { c = 10; } }; class D : public B, public C // 24,变量a,b,c,d共16,B的虚基类指针8,C的虚基类指针8,通过控制变量,猜测还加上了虚函数表?? { public: int d; virtual void add() { c = 10; } }; int main() { A a; B b; C c; D d; cout << sizeof(a) << endl; cout << sizeof(b) << endl; cout << sizeof(c) << endl; cout << sizeof(d) << endl; return 0; }
类模板 模板类
类模板是一个模板,不是一个实在的类,定义中用到通用类型参数。
模板类是实在的类定义,是类模板的实例化。模板类中的参数被实际类型替代。
STL
说说STL
广义上说,STL包含:算法,容器,迭代器。算法和容器可以通过迭代器无缝连接
详细来说:包含容器,适配器,迭代器,仿函数,算法,空间适配器
迭代器返回的是引用
push_back()调用构造函数和拷贝构造函数,push_emplace()只调用构造函数
新特性
说一说C++11的新特性
**auto关键字 ** 编译器自动推断类型
decltype 求表达式类型
auto要求变量必须初始化,而decltype根据表达式推导出变量类型,与=右边的值无关
int a = 0; decltype(a) b = 3.3; decltype(a) c; cout<<b<<endl;// 输出3
**新增三种智能指针 ** shared_ptr 使用引用计数,引用计数为0时才释放内存
move和右值引用
c++98中就有引用,但是一般只允许引用左值。或者用常量左值来引用右值。这样的话右值不能修改,没有意义。c++11提出右值引用,&&
与左值相同,右值引用也必须立即初始化。
int && a = 10; a = 100; cout<<a<<endl;// 输出100
move将某个左值转化为右值
空指针 nullptr 是右值常量,专门用于初始化空类型指针。nullptr_t是c++11新增的类型,而nullptr是该类型的一个实例对象。nullptr可以隐式转换为任意类型的指针。
在c++中,NULL即为0 #define NULL 0
,因为c++不能将 void*
隐式转换为其他类型指针,重载整形时会出现二义性,NULL其实是 int
,而不是空指针。
lambda表达式
正则表达式
哈希表无序容器
统一的初始化方法
初始化列表,(大括号)c++11允许变量名后直接跟上初始化列表,来进行对对象的初始化。
int a{3}; pair<int,int> p{2,3};
成员变量默认初始化
构建类不需要用构造函数。
class A{ int a = 3; };
基于范围的for循环
vector<int> vec; for(int x:vec){ }
智能指针
简单、安全地管理动态内存。智能指针是具有指针行为的对象。定义在memory头文件中
c++11 摈弃了auto_ptr
会出现引用的一个对象被删除多次
shared_ptr
允许多指针指向同一对象,共享所有权,当最后一个智能指针销毁时,对象销毁
unique_ptr
独占指向的对象,互斥所有权,只有一个指针可以指向对象。使用一般的拷贝语义不可以用赋值,但是使用move()能够将一个unique_ptr赋给另一个(所有权转移)
-
move()会返回一个对象,使用了移动构造和右值引用
-
可以delete[] new [] ,也就是可用于数组,而auto_ptr是不可以的
weak_ptr
弱引用,指向shared_ptr管理的对象。weak_ptr只提供一种访问手段,它的构造和析构不会引起引用计数的变化,和shared_ptr之间可以相互转化,使用lock函数可以获得shared_ptr。
weak_ptr用来解决shared_ptr互相引用产生的死锁问题。
#include <iostream> #include <memory> using namespace std; class B; class A{ public: shared_ptr<B> _pb; ~A(){ cout<<"A 2"<<endl; } }; class B{ public: shared_ptr<A> _pa; ~B(){ cout<<"B 2"<<endl; } }; int main() { shared_ptr<A> pa(new A()); shared_ptr<B> pb(new B()); pb->_pa = pa; pa->_pb = pb; cout<<pb->_pa.use_count()<<endl; cout<<pa->_pb.use_count()<<endl; return 0; }
由于pa,pb相互引用,要跳出函数时,两者的引用计数还是1,导致析构函数没有调用,资源没有释放。改用weak_ptr即可
class A{ public: weak_ptr<B> _pb; ~A(){ cout<<"A 2"<<endl; } };
不可以通过弱指针直接访问对象的方法,需要使用.lock()转化为shared_ptr
这篇关于【春招预热】C++回炉重造的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享