【春招预热】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的指针包含一个指向该类虚函数表的指针,而虚函数表指向该类的虚函数,因此每个类的对象都会调用自己类的虚函数

静态成员函数、内联函数、友元函数和构造函数不能被说明为虚函数,析构函数可以

为什么

  1. 不行。虚函数需要虚表,而虚表存储在对象的空间中,调用构造函数前,对象没有实例化,还没有虚表
  2. 没有意义。构造函数在对象创建时被调用,并不会存在一个父类指针调用子类构造函数的情况
  3. 可以保证释放基类指针时释放子类空间,不会内存泄漏

深拷贝和浅拷贝

浅拷贝,只赋值值,两个对象可能指向同一块内存,只是不同的别名

深拷贝,申请相同的空间,再赋值,这样就是两块不同的地址,之间的值相同。

移动构造函数

转移所有权,原对象将丢失其内容。当使用一个无名对象来对一个新对象构造初始化时,移动拷贝构造被调用。

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++回炉重造的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程