C++ 零碎笔记

2021-07-03  本文已影响0人  吃掉夏天的怪物
image.png

常引用(Const Reference)

image.png

输出的是30和10

image.png

struct和class的区别

⭐ 以前都是用struct模拟类,用函数指针

image.png image.png image.png
image.png
上面代码中person对象、pPerson指针的内存都是在函数的栈空间,自动分配和回收的。
struct和class的区别就在权限上
实际开发中,用class表示类比较多

对象的内存布局

image.png

(内存对齐不大会...)


image.png

编译器在成员函数里提供一个指针this ,比如利用person1去调用run的时候就会将person1的地址值传进去。也即,this指针存储着函数调用者的地址,this指向了函数调用者。


image.png
image.png

点左边只能是对象


image.png

汇编代码,中括号里放的绝对是地址

原理:如何利用指针间接访问所指向对象的成员变量?
1.从指针中取出对象的地址
2.利用对象的地址 + 成员变量的偏移量计算出成员变量的地址
3.根据成员变量的地址访问成员变量的存储空间

如果用对象调用函数,会将对象的地址值传进去
如果通过指针间接的调用函数,会将指针中存储的地址值传进去。而不是将指针自己的地址传进去。

中断: interrupt
cc -> int3 : 起到断点的作用

(因为函数栈空间不小心被当成栈空间来执行也会被停止)

ip指针(寄存器)指向下一条需要执行的机器指令的地址。
一旦执行完一条指令 ip +=g刚执行完的机器指令的大小

调用函数、执行函数代码,其实就是CPU在访问代码区的内存(指令)

调用函数的时候,需要分配额外的存储空间来存储函数内部的局部变量。
函数在代码段的空间,是用来放函数代码(机器指令),代码区是只读的。也就是在代码区执行的同时,会在栈空间分配连续的一段存储空间给函数用。

函数代码存储在代码区,局部变量存储在栈空间。

封装

内存空间的布局

每个应用都有自己独立的内存空间,其内存空间一般都有一下几大区域。


image.png

堆空间

在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存。

堆空间的申请/释放

//指针的类型,完全看你想要什么类型的
int * p = (int *)malloc(4);//返回这四个字节的首地址(void*)
*p = 10;//这样就将10放入了堆空间
free(p);//malloc一次就free一次,申请4个字节也释放4个字节,不能只释放一部分
int *p = new int;
*p = 10;
delete p;

char *p = new char;//申请一个字节
*p = 10;
delete p;
char *p = new char[4];
delete[] p;

注意

堆空间初始化

image.png
//malloc是没有进行初始化的
memset(p,0.size);//从p地址开始,连续的size个字节中的**每一个字节**❗都设置为0
image.png

其他元素内初始化为0

int* p1 = new int;
int* p2 = new int();//右边多个小括号,会调用memory set。但是可能编译器不同比如Mac上的Xcode new int可能会初始化
int* p3 = new int(3);
image.png

对象的内存

对象的内存可以存在于3种地方

//全局区
Person g_person;
int main(){
   //栈空间
   Person person;
   //堆空间
   Person *p = new Person;//指针变量p在栈空间,Person变量在堆空间
   return 0;
}

构造函数(Constructor)

构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作。

特点:

注意

一个广为流传的、很多教程\书籍都推崇的错误结论:
默认情况下,编译器会为每一个类生成空的无参的构造函数原因:24分左右
正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数。
(哪些特定情况?以后再提)

默认情况下成员变量的初始化



析构函数(Destructor)

析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作。

特点

函数名以~开头, 与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数

注意

内存管理




懒得截图24min左右

声明和实现分离


类的声明一般放在.h文件
类的实现.cpp文件

对于引用:
系统自带的< >
我们实现的""

命名空间

命名空间可以用来避免命名冲突
命名空间不允许内存布局❓❓


using MJ::g_age;//using后不用加namespace 吗?可以

不可以,因为有二义性。如果对g_age加上前缀则可以。

命名空间是可以嵌套的


image.png

有个默认的全局命名 空间


1624546640(1).png

命名空间的合并


image.png

C++不能靠文件夹解决命名冲突

继承

继承,可以让子类拥有父类的所有的成员(变量\函数)


image.png

Java

//基类: 最基本的类。其他所有的类最终都会继承自它。类的老祖宗。




从父类继承的成员变量会排布再前面:

子类内部访问父类成员的权限,是以下2项中权限最小的那个

class的继承默认是private继承,struct是public继承

初始化列表

特点

构造函数的互相调用 (不太明白❗)

父类的构造函数

子类的构造函数默认会调用父类的无参构造函数

如果子类的构造函数显示地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数

如果父类缺少无参的构造函数,子类的构造函数必须显示调用父类的有参构造函数。

构造、析构顺序

父类指针、子类指针

父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)⭐


多态⭐

多态的要素

虚函数

C++中的多态通过虚函数(virtual function)来实现
虚函数:被virtual修饰的成员函数

如果父类是虚函数,子类重写自动是虚函数
只要再父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类可以省略virtual)。

只要看到E8开头的机器指令,对应就是直接call(直接写死的地址)。
FF开头的call,间接call(取出寄存器、内存中的东西,然后call)

虚表

image.png
(把函数地址和对象绑定在一起)
(有虚函数就有虚表,虚表里放的是虚函数地址)
猫有猫的虚表,猪有猪的虚表。猫和猪的虚表是分开的。

所有的cat对象(不管在全局区、栈、堆)公用同一份虚表

F9然后按下F5,来看一下汇编


一个虚函数都没有,那就没有虚表,直接看指针类型是什么。
只有一个虚函数, 就放了一个
两个都是虚函数,子类只重写了一个。放两个Animal一个Cat一个

父类是虚函数,子类重写也是虚函数。但是子类是虚函数,父类可不是。


如果想执行父类的方法后再执行自己的方法.png

虚析构函数

含有虚函数的类,应该将析构函数声明为虚函数(虚析构函数)


如果存在父类指针指向子类对象的情况,樱桃该将析构函数声明为虚函数.png

(构造函数会先调用父类再调用子类)

纯虚函数

纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范。


动物是能跑能叫的.png

抽象类(Abstract Class)

Java:抽象类、接口
OC:协议

多继承

C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
(先继承哪个父类,内存中 哪个父类在前面)

多继承构造函数

多继承-虚函数

如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表。


同名函数/成员变量

菱形继承

菱形继承带来的问题

虚继承 ❓❓❓

虚继承可以解决菱形继承带来的问题

Person称为虚基类


image.png 虚继承代码示例.png

菱形继承的应用

静态成员(static)

静态成员:被static修饰的成员变量\函数

可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员变量)

静态成员变量

静态成员函数

(学东西要多问几个为什么)



(外面的全局变量和静态变量都是放在data segment)

static经典应用场景

一、统计创建了多少量车

二、单例模式

单例模式:设计模式的一种,保证某个类永远只创建一个对象。
1.构造函数\析构函数 私有化
2.定义一个私有的static成员变量指向唯一那个单例对象
3.提供一个公共的访问单例对象的接口
(用指针是因为堆空间更加灵活)
❗** 实际开发中需要考虑多线程问题。(一般共享的东西,都需要考虑线程安全问题)**

单例模式示例.png
image.png
delete ms_rocket,仅仅是将指针指向的堆空间回收掉。但是指针变量依然存储着对空间的地址值。

delete的误区

delete后依然是有值的,需要自己去清空。即使P = NULL也只是指针为NULL,原来指向堆空间存放的东西也没有清空,因为没必要。

image.png

const成员

const成员:被const修饰的成员变量、非静态成员函数

const成员变量

const成员函数(非静态)

-const 关键字写在参数列表后,函数的声明和实现都必须要带const

引用类型成员

引用类型成员变量必须初始化(不考虑static)

拷贝构造函数(Copy Constructor)

浅拷贝:指针类型的变量只会拷贝地址值
深拷贝:将指针指向的内容拷贝到新的存储空间

调用父类的拷贝构造函数

image.png

默认,会将已经存在对象的所有字节覆盖新对象的所有字节。


这里并没有调用拷贝构造函数(仅仅是简单的赋值操作).png

构造函数是在对象创建完马上调用的

❓ ??没有赋值就应该是0xcc?

子类的构造函数,默认会去调用父类无参的构造函数

浅拷贝、深拷贝

浅拷贝

如果要实现深拷贝,就需要自定义拷贝构造函数

// 有问题的代码
    const char* name = "changliang"; //注意这是个常量字符串,char前面需要加上const
    //C语言里字符串本质就是字符数组,\0是字符串的结束标志
    char name2 []= { 'a','b' ,'v','\0'};
    cout << name2<<strlen(name2) << endl;
栈空间指向堆空间,堆空间又指向栈.png

❗ 堆空间指向栈空间都很危险,因为栈空间是不能自己去控制生命周期的,它随时都可能会被干掉。而堆空间可以自己控制。就可能指向已经被回收的内存。

所以应该这么做:


image.png

如果new一个东西的时候,右边如果加了大括号或小括号,会将申请的堆空间的数据清零。那为什么要进行清零操作呢,我要保证申请的一段内存空间最后一个字节是/0。


image.png
#include<iostream>
using namespace std;
class Car{
    int m_price;
    char* m_name;
public:
    //Car(int price =0,char* m_name=NULL):m_price(price),m_name(m_name){}
    Car(int price = 0, char* name = NULL) :m_price(price) {
        if (name == NULL) return;
        //申请新的空间
        m_name = new char[strlen(name)+1] {};
        //拷贝字符串数据到新的堆空间
        memcpy(m_name, name,sizeof(name));
    }
    ~Car() {
        if (m_name == NULL)return;
        delete[] m_name;
    }
    void display() {
        cout << "Price is " <<m_price << ",name is  " << m_name << endl;    

    }
};
int main() {
    const char* name = "changliang"; //注意这是个常量字符串,char前面需要加上const
    //C语言里字符串本质就是字符数组,\0是字符串的结束标志
    char name2 []= { 'a','b' ,'v','\0'};
    cout << name2<<strlen(name2) << endl;
    Car* car = new Car(100, name2);
    car->display();
    getchar();
    return 0;

}

image.png

上述代码还是存在问题,如图


可能会有double free问题.png

对象类型参数和返回值(不建议这么干,一般用引用)

使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象。


作为函数参数.png 作为返回值.png

(main函数会预留一个car对象的地址,并传给test2,test2在销毁前将car拷贝构造到main的地址空间。然后car2 = test2();就仅仅是赋值,不存在拷贝构造)

image.png

(直接把test2返回的car拷贝构造给car3)

匿名对象(临时对象)

匿名对象:没有变量名、没有指针指向的对象,用完后马上调用析构。

匿名对象(一次性对象,用完就扔).png 回顾一下.png 让构建出来的对象直接变成参数了.png image.png

隐式构造

C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。


会调用单参数的构造函数.png

image.png image.png

编译器自动生成的构造函数

很多教程都说:编译器会为每一个类都是生成空的无参的构造函数。错❌

说白了根本没有调用Person构造函数.png
C+的编译器在某些特定情况下,会给类自动生成无参的构造函数,比如 :

总结一下

[图片上传失败...(image-2fa9b5-1625309958498)]

image.png image.png 且最前面的四个字节是用来存储虚表地址.png

友元

友元函数(friend只要放class的大括号里就行)).png

内部类

如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)

内部类的特点

内部类,可以控制类的访问权限.png 不影响外部类内存布局.png 内部类声明和实现分离1.png 内部类声明和实现分离2.png

局部类

在一个函数内部定义的类,称为局部类

局部类的特点

一编译函数代码都会放在代码区class Car和栈空间一点关系没有.png

(执行test函数只有 Class Car不会执行,下面两句才是会被执行的)

局部类和内部类,仅仅是访问权限的问题。把类放在函数内,就表示只有函数里面访问。不会影响内存布局。

运算符重载(operator overload)

image.png
8分钟
++.png
image.png 左移运算符的重载得是全局的,否则左边就跟类有关了。但是这样实现不能连续打印.png
为了能够连续打印.png

运算符重载输入

image.png

十五分钟左右,(cout<<p1) = count为什么会报错的问题

image.png

单例模式的完善(好早之前讲的单例模式,说讲完重载再完善,还真就!)

因此拷贝构造函数也需要私有化.png
image.png image.png

运算符重载父类

image.png

仿函数

仿函数: 将一个对象当作一个函数来使用
对比普通函数,它作为对象可以保存状态。

image.png image.png

运算符重载注意点

image.png

模板(template)

泛型,是一种将类型参数化以达到代码复用的计数,C++中使用模板来实现泛型。
(写一份代码,编译器帮忙生成多份。如果没有用到模板,编译器不会去生成,用到什么生成什么)

模板的使用格式如下

image.png
image.png image.png image.png image.png

模板 - 动态数组

抛出异常.png image.png

模板 - 类

void* 万能指针,只要对象是地址就行


image.png

动态数组的删除

类型转换

image.png

(1) const_cast

image.png
没有区别,只是不同语言的风格骗骗编译器.png

(2) dynamic_cast ⭐

一般用于多态类型的转换,有运行时安全检测

stu1直接变空.png
image.png
image.png

(3) static_cast

reinterpret_cast

C++标准的发展

C++标准的发展.png

C++11新特性

auto

decltype

image.png

nullptr

int a = NULL;//
int *b = NULL;//以后对指针用建议用nullptr⭐

快速遍历

image.png

更加简洁的初始化方式

image.png

Lambda表达式 ⭐⭐

有点类似于JavaScript中的闭包、IOS中的Block,本质就是函数

Lambda表达式.png
image.png
//这样只是定义了一个lambda函数
[]{
  cout<<"func"<<endl;
 };
//写个小括号去调用一下
([]{
  cout<<"func"<<endl;
 };)()
image.png
image.png

❗ 想到函数指针还不大会

image.png 默认的捕获是值捕获.png
//地址捕获
auto func = [&a] {
  cout<< a <<endl;
 };

Lambda表达式 - 外部变量捕获

image.png
image.png

Lambda表达式 - mutable

image.png image.png

C++14(并未普及到企业开发中,了解即可)

image.png

C++17(并未普及到企业开发中,了解即可)

image.png
设置C++版本.png

(只要限制作用域的都是编译器特性)

错误

常见错误

异常

image.png 卡一段时间就闪退.png

一旦有抛出异常,后面的代码都不会执行。除非有人把它catch住了。 (如果没有catch会一直往外抛,直至操作系统 )


image.png
image.png
image.png

throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前代码,去上一层哈纳树中查找,如果都找不到匹配的catch,整个程序就会终止。

异常的抛出声明

image.png

自定义异常类型

image.png image.png image.png const对象也只能调用const函数.png
那子类的函数也都得加个const.png

拦截所有异常

image.png

标准异常

image.png image.png

智能指针(Smart Pointer)

传统指针存在问题

智能指针就是为了解决传统指针存在的问题

(智能指针内存在栈空间)

auto_ptr: 属于C++98标准,在C++11中已经不推荐使用(有缺陷, 比如不能用于数组 )

//可以理解为:智能指针p指向了对空间的Person对象
auto_ptr<Person> p(new Person);
//Person对象的生命周期跟随智能指针

shared_ptr: 属于C++11标准

unique_ptr: 属于C++11标准

内存泄漏.png
delete p;
p = nullptr;//防止利用p再指
智能指针的自实现.png 如果加上explicit则不能隐式调用.png

注意智能指针千万别指向栈空间的东西,要指堆空间


double free.png
自制智能指针.png

shared_ptr

shared_ptr的设计理念

多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用范围内结束时,

shared_ptr<Person> p1(new Person());
shared_ptr<Person>p2(p1);
shared_ptr<Person> ptr1(new Person[5]{},[](Person* p){delete[] p;});//如果非不些[]就得传lambda表达式
shared_ptr<Person[]>persons(new Person[5]{});//√

shared_ptr的原理

shared_ptr两次析构问题

两次析构.png

shared_ptr循环引用问题

导致的问题就是对象无法销毁


image.png
image.png
image.png
image.png

weak_ptr

image.png
weak_ptr解决循环引用.png

unique_ptr

外挂项目

外挂界面

Windows平台的桌面开发

上一篇下一篇

猜你喜欢

热点阅读