C++核心概念

此为学习笔记,参考的教程链接:https://www.bilibili.com/video/BV1et411b73Z?p=84
代码笔记链接:https://github.com/JamieLL/cpp_beginner

1. 内存分区

  • 代码区
    • 共享、只读
  • 全局区:全局变量、静态变量(static)、常量(字符串、const修饰的全局常量)
  • 栈区:编译器自动分配释放,如函数参数值、局部变量
    • 不要返回局部变量的地址,栈区数据在函数执行后自动释放
  • 堆区:程序员分配释放,程序结束时系统也会回收
    • 用new在堆区开辟内存:int* p = new int(10);
    • 释放:delete p;, delete[] arr;

2. 引用

给变量起别名:数据类型 &别名 = 原名,int &b = a;

  • 引用必须初始化,且之后不能更改
  • 引用传递,可以用形参修饰实参,即可以不用指针的地址传递
    • func(int& a, int& b){}, 调用时func(a, b)
  • 引用作为函数返回值
    • 不要返回局部变量的引用
    • 函数的调用可以作为左值
int& aka() {
	static int a = 10;
	return a;}
int main() {
	int &ref = aka();
	aka() = 1000; //赋值操作,于是ref2 = 1000;
}
  • 引用的本质是一个指针常量
    • int& ref1 = a等价于int* const ref1 = &a;
    • ref1 = 20;,内部发现ref1是引用后,等价于*ref1 = 20;
  • 常量引用:const用来修饰形参,防止误操作,func(const int& a)

3. 函数提高

  • 形参可以有默认值
    • 如果某个位置有默认值了,其后的所有形参都必须有默认值
    • 函数声明和实现只能有一个有默认参数
  • 占位参数:函数中只写数据类型,在调用时必须补上,int fun(int a,int), int func(int a,int =10)
  • 函数重载:同一个作用域下,函数名相同, 函数参数类型不同或者个数不同或者顺序不同
    • 引用作为重载条件时:int& a和const int& a;当传入的是变量时调前者,常量调后者。
    • 函数重载碰到默认参数:产生歧义,报错

4. 类和对象

面向对象:封装、继承、多态

4.1 封装

定义
  • 类中的属性和行为统称为成员
    • 属性:成员属性、成员变量
    • 行为:成员函数、成员方法
  • 实例化对象
  • 访问权限:
    • public公共:成员类内可以访问,类外可以访问
    • protected保护:成员类内可以访问,类外不可以访问,子类可以访问
    • private私有:成员类内可以访问,类外不可以访问,子类不可以访问
注意事项
  • struct和class的区别:struct默认权限是public,class默认权限是private
  • 成员属性设置为private:可以自己控制读写权限、对于写权限可以检测数据的有效性

4.2 对象特性

构造函数和析构函数
  • 构造函数:创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动
    • 类名() {},可以有参数,可重载
    • 无参构造、有参构造、拷贝构造
      • 调用默认(无参)构造函数时,不要加(),否则会认为是函数声明
      • 值传递时会调用拷贝构造,值方式返回局部对象时也会调用拷贝构造
    • 默认情况,c++至少给一个类添加默认构造函数(无参)、默认析构函数(无参)、默认拷贝构造函数、operator=
      • 若用户定义有参构造,则c++不再提供默认无参构造,但会提供默认拷贝构造
      • 若用户定义拷贝构造,则c++不再提供其他构造函数
  • 析构函数:对象销毁前系统自动调用,执行一些清理工作
    • ~类名() {},不可以有参数,不能重载
其他
  1. 深拷贝与浅拷贝
  • 浅拷贝:简单的赋值拷贝
    • 可能产生的问题:堆区的内存重复释放
  • 深拷贝:在堆区重新申请空间,进行拷贝
  • So,如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
  1. 初始化列表
    初始化列表初始化属性:Student(int a, int b) :m_a(a), m_b(b) {}
  2. 类对象作为类成员
  • 当其他类对象作为本类成员,构造时先构造其他类,再自身;析构与构造相反
  1. 静态成员
  • 静态成员变量:类内声明,类外要初始化
  • 静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量
    • 可以直接用类名访问Person:func();
  1. 类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上
  • c++会给空对象也分配一个字节空间,是为了区分空对象占内存的位置;有成员时就算成员的大小
  1. this指针:指向被调用的成员函数所属的对象
  • 当形参和成员变量同名时,用this区分
  • 在类的非静态成员函数中返回对象本身,可return *this;
  1. 空指针访问成员函数
  • 但是注意此时空指针不能访问变量(报错
  1. const修饰成员函数:常函数
  • void func() const {},本质修饰的是this指针
  • 不能修改成员属性,但若成员属性声明时加了mutable就可以修改
  • const修饰的对象为常对象,只能调用常函数

4.3 友元

friend,让一个函数或者类访问另一个类的私有成员

  • 三种实现:全局函数、类、成员函数
  • 代码顺序:a类是b类的friend,声明b->定义a->定义b->实现b->实现a,类中的友元函数得在类外实现,否则报错

friend function本质上来说是global的,不是class scope的,所以只能在类里面声明,在类外面定义

4.4 运算符重载

对已有的运算符重新定义,以适应不同的数据类型

  • 可通过成员函数、全局函数重载
  • 运算符重载也可以发生函数重载
  • 对内置的数据类型的运算不能改变
  1. 加号+
  2. 左移运算符<<
    可输出自定义的数据类型,配合友元
  • 一般不用成员函数来重载左移运算符:无法实现cout在左侧
  • ostream cout
  1. 递增运算符++
    可实现自己的整型数据
  • 区分前后置:前operator++(),后operator++(int)
  • 前置返回引用,后置返回值
  1. 赋值运算符=
  • 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
  1. 关系运算符>, <, >=, <=, ==
    可以让两个自定义类型对象进行对比操作
  2. 函数调用运算符()
  • 由于重载后的使用方法很像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,参数灵活

4.5 继承

  • 上一级的共性+自己的特性
  • 好处:减少重复代码
  • 语法:class Son : public Father {};
  • 子类也称为派生类,父类也称为基类
  • 继承方式:
    1. 公共继承:父中的public和protected原样继承
    2. 保护继承:父中的public和protected继承为protected
    3. 私有继承:父中的public和protected继承为private
    • 父类中所有的非静态成员属性都会被子类继承
    • 关于父类的private,会被继承(体现在sizeof),只是被隐藏了,访问不到
    • 用“Developer Command Prompt for VS 2019”验证:
      • 进入cpp所在路径
      • dir查看目录
      • cl /d1 reportSingleClassLayout类名 "文件名"可查看该class的结构、size等信息
  • 继承中的构造和析构顺序:
    • 父构造、子构造、子析构、父析构(析构与构造顺序相反)
  • 继承同名成员:
    • 子类成员直接访问,父类同名成员加作用域s.Base::m_A
    • 如果子类中出现了与父类同名的成员函数,则会隐藏父类中所有的同名成员函数,故访问父类中的都得加作用域(就算参数不同也不行)
  • 继承同名静态成员:
    • 子类成员直接访问,父类同名成员加作用域
    • 通过子类类名访问父类作用域下的静态成员:Son::Base::m_A
  • 多继承
    • 语法:class Son : public Base1, public Base2{};
    • 可能会引发父类中同名成员出现,要加作用域区分,故c++实际开发中不建议多继承
  • 菱形继承/钻石继承
    • 两个父亲继承同一个爷爷,又有一个儿子同时继承两个父亲
    • 引发的问题:儿子会有两份爷爷的数据,要加作用域;但是儿子只需要一份
    • 利用虚继承,解决菱形继承的问题
      • class Son:virtual public Base{};
      • 此时儿子只有一份爷爷的数据了,但size是两份
      • 继承的是vbptr虚基类指针,指向vbtable

4.6 多态

  • 分为两类:
    • 静态多态:函数重载、运算符重载
    • 动态多态:派生类和虚函数实现运行时的多态
    • 区别:静态多态的函数地址早绑定(编译阶段),动态多态的函数地址晚绑定(运行阶段)
  • 动态多态满足条件:有继承关系,子类重写父类的虚函数
    • 重写指:函数返回值类型、函数名、参数列表完全相同
  • 动态多态使用:父类的指针或者引用执行子类对象
  • 多态原理:
    • vfptr虚函数(表)指针,指向vftable虚函数表
      • 指针占4个字节
    • vftable表内部记录虚函数的地址
    • 当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址
    • 当父类的指针或者引用指向子类对象时,发生多态
  • 多态优点:代码组织结构清晰、可读性强、利于前期和后期的扩展以及维护
  • 纯虚函数和抽象类
    • 纯虚函数:virtual 返回值类型 func(...) = 0;
    • 当类中有了纯虚函数,此类称为抽象类
    • 抽象类特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类
  • 虚析构和纯虚析构
    • 若子类有申请堆区,则父类指针在释放时无法调用到子类的析构代码,内存泄漏,故将父类的析构函数改为虚析构或纯虚析构
    • 共性:可以解决父类指针释放子类对象,都需要具体的函数实现
    • 区别:若是纯虚析构,则该类属于抽象类,无法实例化对象
    • 虚析构:
      • virtual ~类名(){}
    • 纯虚析构
      • vitual ~类名() = 0;, 类名::~类名(){}
      • 记得要具体实现,且在类外实现

5 文件

  • #include <fstream>
    • ofstream:写
    • ifstream:读
    • fstream:读写
  • 文本文件:以文本的ASCII码形式存储
  • 二进制文件:以文本的二进制形式存储

5.1 文本文件

  • 写文件
    • 头文件:#include <fstream>
    • 创建流对象:ofstream ofs;
    • 打开文件:ofs.open("路径",打开方式);
      • ios::in读文件
      • ios::out写文件
      • ios::app追加方式写文件
      • ios::trunc若文件存在先删除,再创建
      • ios::binary二进制方式
      • ios::ate初始位置在文件尾
      • 可配合使用|,如ios::binary | ios::out
    • 写数据:ofs << "写入的数据";
    • 关闭文件:ofs.close();
  • 读文件步骤类似,有4种读取方式(见代码)

5.2 二进制文件

  • ios::binary
  • 写文件
    • 函数原型ostream& write(const char* buffer, int len);
    • 字符指针buffer指向内存中一段存储空间,len是读写的字节数
  • 读文件
    • 函数原型istream& read(char *buffer, int len);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值