Cpp_learning_note_2

Posted on Mar 17, 2021

[toc]

reference:《黑马》C++系列教程 基础 核心 提高

本篇主要参考核心篇进行记录

内存分区

  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用new在堆区开辟内存,释放利用操作符 delete,note释放数组 delete 后加 []

引用

  • 不要返回局部变量引用,可以返回静态变量引用 static

  • 引用的本质,在C++内部实现是一个指针常量

int a = 10;
int& ref = a;
//自动转化为int* const ref = &a; 指针常量是指针指向不可改
  • 在函数形参列表中,可以加const修饰形参,防止形参改变实参
int& ref = 10;  //是错误的,引用本身需要一个合法的内存空间,不能直接赋值
const int& ref = 10;
//加入const,编译器会优化代码,int temp = 10; const int& ref = temp;

函数重载

//1.引用作为重载条件
void func(int& a)
void func(const int& a)
int a = 10;
func(a);
func(10);//调用不同的函数

//2.函数重载遇到函数默认参数
func2(int a, int b = 10)
func2(int a)
//这样的定义在调用的时候会报错
func2(10);//产生歧义,不能做出调用函数的判断

类和对象

OOP - 封装、继承、多态

一、about

  • 语法:class 类名{ 访问权限: 属性 / 行为};
  • 类在设计时,可以把属性和行为放在不同权限下,加以控制

访问权限有三种:

– public 公共权限: 类内可以访问,类外可以访问(只允许本类的成员函数访问)

– protected 保护权限 :类内可以访问,类外不可以访问 (只允许子类及本类的成员函数访问)

– private 私有权限: 类内可以访问,类外不可以访问(可以被任意实体访问)

  • 在C++中 struct 和 class 唯一的区别就在于默认的访问权限不同,struct默认权限为公共,class默认权限为私有

二、concerning 对象

  • 对象的初始化和清理 – 构造函数和析构函数

如果编程者不提供,编译器会自动提供空实现的构造和析构函数(这时候编译器自动添加的拷贝构造对属性的拷贝是浅拷贝

–构造函数语法: 类名(){}

  1. 构造函数没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以参数,可能发生函数重载
  4. 程序在调用对象的时候会自动调用构造,且只调用一次

分类 按参数:有参构造、无参构造 按类型:普通构造、拷贝构造

调用方式 括号法、显式法(类似java)、隐式转换法

*tips:

  1. 调用无参构造函数不能加括号,加了编译器认为这是一个函数声名;

  2. 不能利用拷贝构造函数初始化匿名对象,编译器认为是对象声明;

  3. 拷贝构造函数的调用时机:

    • 使用一个已创建好的对象初始化新的对象
    • 值传递的方式给函数参数传值
    • 以值的方式返回局部对象

– 析构函数语法:~类名(){}

  1. 析构函数没有返回值也不写void
  2. 函数名称与类名相同,前面加上符号~
  3. 析构函数不可以有参数,不能发生重载
  4. 程序在对象销毁前会自动调用析构,且只调用一次

(一)深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间进行拷贝操作

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的在delete时出现的问题

new -> 在堆空间创建一块内存并返回内存地址给指针

int* p = new int(10);//这里定义变量类型为指针

(二)初始化列表

语法:构造函数(): 属性1(值1), 属性2(值2), …{}

只有构造函数可以使用初始化列表语法,引用数据成员与const数据成员必须使用这种语法,因为它们在创建时就需要初始化

class Person{
public:
	Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {}
private:
	int m_A:
	int m_B;
	int m_C;
}

(三)类对象作为类成员

当类中成员时其他类对象时,我们称该成员为 对象成员

构造的顺序是:先调用对象成员的构造,再调用本类构造。析构的顺序与构造相反

(四)静态成员

在成员变量和成员函数前加上关键字static,称为静态成员

  • 静态成员变量(2种访问方式:通过对象、通过类名)

    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
    class Person{
    public:
        static int m_A;
    private:
        static int m_B;
    }
    int Person::m_A = 10;
    int Person::m_B = 10;//类外初始化
    
    cout << Person::m_A << endl;//通过类名访问
    cout << Person::m_B << endl;//会报错,没有访问权限
    
  • 静态成员函数

    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

三、C++对象模型和this指针

在C++中,类内的成员变量和成员函数 分开储存,只有非静态成员变量才属于类的对象上。非静态成员变量占用对象空间,而静态成员变量和成员函数(静态/非静态)都不占用对象空间,所有非静态成员函数共享一个函数实例。

  • this指针 指向被调用成员函数所属的对象

    • this指针是隐含在每一个非静态成员函数内的一种指针
    • 不需要定义可以直接使用
    • 用途:当形参和成员变量同名时,用this指针区分;在类的非静态成员函数中返回对象本身,可使用return *this
    • 空指针也可以调用成员函数,但是当成员函数用到了this指针则不可以,这个时候需要对指针进行非空判断,保证代码的健壮性
    Person* p = NULL;
    
    if(this == NULL) return;//非空判断
    
    • 常函数:成员函数后加const​,在常函数内不可以修改成员属性,但是当成员属性声明时添加mutable关键字,在常函数内仍可以进行修改
    void func() const {...}
    public:
    	int m_A;
    	mutable int m_B; 
    
    • 常对象:声明对象前加const,常对象只能调用常函数,不能修改成员变量的值,但是可以访问
    const Person person;
    

四、友元 -> 让一个函数或者类访问另一个类中的私有成员

关键字 friend

三种实现

  • 全局函数左右元
  • 类做友元
  • 成员函数做友元

五、运算符重载 -> 对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型

函数名由关键字 operator 和其后要进行重载的运算符符号构成的

通俗理解就是将数的相加、输出、比较判断等运算重载为直接对对象进行操作

六、继承

class A: public B { }; 其中A类称为子类 或 派生类,B类成为父类 或 基类

派生类中的成员包含从基类继承的和自己增加的成员

  • 继承方式 语法: class 子类: 继承方式 父类
    • 公共继承 public
    • 保护继承 protected
    • 私有继承 private
publicprotectedprivate
公有继承publicprotected不可见
保护继承protectedprotected不可见
私有继承privateprivate不可见

tips:

  1. 父类中私有成员也被子类继承下去了,只是由编译器隐藏后不能访问

  2. 继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

  • 继承同名成员的处理方式
    • 访问子类同名成员 直接访问即可
    • 访问父类同名成员 需要添加作用域
class Base {
public:
    Base(){m_A = 100;}
    void func(){}
    int m_A;
}
class Son: public Base{
public:
    Son(){m_A = 200;}
    void func(){}
    int m_A;
}
void main(){
    Son s;
    s.func();//直接访问子类同名成员
    s.Base::func();//添加作用域访问父类同名成员
}//这里是子类以对象的方式进行访问

*tips:对于同名静态成员处理方式与上面相同,不过有通过对象和通过类名两种访问方式

  • 多继承语法:class 子类: 继承方式 父类1, 继承方式 父类2 …
  • 菱形继承(草泥马的例子好形象2333)

羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

class Animal
{
public:
	int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}

*tips:采用虚继承解决菱形继承的问题,虚继承的目的是让某个类做出声明,承诺愿意共享它的基类

七、多态

分类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

区别:静态–函数地址早绑定 - 编译阶段确定函数地址

动态–函数地址晚绑定 - 运行阶段确定函数地址

class Animal{
public:
    virtual void speak(){}//函数前加上virtual关键字,变成虚函数,编译器在编译的时候就不能确定函数调用
};
class Cat:public Animal{//继承Animal类
public:
    virtual void speak(){}//Q:这里为virtual为什么可以省略不写
};
class Dog:public Animal{
public:
    virtual void speak(){}
};
void DoSpeak(Animal & animal){
    animal.speak();
}
int main(){
    Cat cat;
    DoSpeak(cat);//传入什么对象,就调用什么对象的寒素
    Dog dog;
    DoSpeak(dog);
    return 0;
}
  • 多态满足条件:1. 有继承关系 2. 子类重写父类中的虚函数
  • 多态使用:父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

在多态中,通常父类中虚函数饿实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

  • 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

当类中有了虚函数(虚构造函数或虚析构函数),这个类也成为抽象类(类中只要有一个纯虚函数就成为抽象类)

抽象类特点:

  1. 无法实例化对象

  2. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

Q:多态使用时,子类中开辟属性到堆区(new),父类指针在释放时无法调用子类的析构代码

A:将父类中的析构函数改为虚析构纯虚析构

  • 虚析构和纯虚析构共性:
    • 可以解决父类指针释放子类对象的问题
    • 都需要有具体的函数实现
  • 虚析构和纯虚析构区别:
    • 纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名 () {}

纯虚析构语法:类内 -> virtual ~类名() = 0; 类外 -> 类名::~类名() {}

文件操作

C++中对文件操作许哟啊包含头文件<fstream>

文件类型:

  1. 文本文件:以ASCII码形式储存在计算机中
  2. 二进制文件:以二进制形式存储在计算机中

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream:读操作
  3. fstream:读写操作

文本文件

  • 写文件
    1. 包含头文件#include<fstream>
    2. 创建流对象ofstream ofs;
    3. 打开文件ofs.open("file_path", way);
    4. 写数据ofs<<"writing data";
    5. 关闭文件ofs.close();
  • 文件打开方式
打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::ate初始位置:文件尾
ios::app追加方式写文件
ios::trunc如果文件存在先删除,再创建
ios::binary二进制方式

文件打开方式可以配合使用,利用|操作符

e.g. 用二进制方式写文件ios::binary|ios::out

  • 读文件

    1. 包含头文件#include<fstream>

    2. 创建流对象ifstream ifs;

    3. 打开文件ifs.open("file_path", way);,利用is_open函数可以判断文件是否打开成功

    4. 读数据 四种方式

      • char buf[1024] = {0};
        while(ifs >> buf){
        	cout << buf << endl;
        }
        
      • char buf[1024] = {0};
        while(ifs.getline(buf, sizeof(buf))){
        	cout << buf << endl;
        }
        
      • string buf;
        while(getline(ifs, buf)){
        	cout << buf << endl;
        }
        
      • char c;
        while((c = ifs.get()) != EOF){
        	cout << c;
        }
        
    5. 关闭文件ifs.close();

二进制文件

打开方式指定为ios::binary

  • 写文件 利用流对象调用成员函数wirte

函数原型 ofstream& write(const char * buffer,int len);

  • 写文件 利用流对象调用成员函数read

函数原型 ifstream& read(char *buffer,int len);