ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【春招预热】C++回炉重造

2022-03-20 13:33:37  阅读:145  来源: 互联网

标签:初始化 函数 int C++ 内存 春招 重造 ptr 指针


本文主要参照[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

标签:初始化,函数,int,C++,内存,春招,重造,ptr,指针
来源: https://www.cnblogs.com/FushimiYuki/p/16029713.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有