黑马c++笔记

4. 控制语句

三目运算符返回的是一个变量,可以继续进行赋值。

(a > b ? a : b) = 100;//就是把两者中比较大的那个的值赋成100
4.1 switch语句

在这里插入图片描述

int main(){

    int num = 0;
    cin >> num;
    switch(num)
    {
        case 1:
            cout << 1 << endl;
            break;
        case 2:
            cout << 2 << endl;
            break;
        default:
            cout << "else" <<endl;
            break;
    }

    system("pause");

}

生成随机数,使用rand()函数,没有头文件

#include<ctime>
srand((unsigned int)time(NULL));
int num = rand() % 100 + 1;

5. 数组

5.1 概述
cout <<  sizeof(arr) / sizeof(arr[0]) << endl;//可以计算出数组的长度
cout << "数组的首地址" << arr <<endl;
cout << "数组的首地址" << &arr[0] <<endl;
//arr = 100; 错误,数组名是常量,因此不可以进行赋值操作

5.2 二维数组

在这里插入图片描述

二维数组的数组名:

  • 可以查看二位数组占用的内存空间
  • 可以查看二位数组的首地址

6. 函数

函数的声明可以有多次,但是定义只能有一次

6.7 函数的分文件编写

函数分文件编写的一般步骤:

  1. 创建后缀名为.h的头文件
  2. 创建后缀名为.cpp的源文件
  3. 在头文件中写函数的声明
  4. 在源文件中写函数的定义
//add.h
#include<iostream>
using namespace std;

//函数声明
int add(int a, int b);
//add.cpp
#include "add.h"

//函数定义
int add(int a, int b){
	return a + b;
}

7. 指针

7.1 指针的基本概念

指针的作用:可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一般使用16进制数来表示
  • 可以使用指针变量保存地址

在32位操作系统下,指针占4个字节;在64位中,指针占8个字节。

7.4 空指针

空指针:指针变量指向内存中编号为0的空间、

用途:初始化指针变量

注意:空指针指向的内存是不可以访问的

int *p = NULL;

0~255的内存区域是系统保留,不可以访问。

野指针:指针指向非法的内存空间

在程序中要尽量避免野指针

7.5 const修饰指针

const修饰指针由三种情况:

  1. const修饰指针——常量指针
  2. const修饰常量——指针常量
  3. const既修饰指针,又修饰常量
const int * p = &a;//常量指针
//特点是:指针的指向可以改,指针指向的值不可以改
*p = 20;//错误
p = &b;//正确
int* const p = &a;//指针常量
//指针的指向不可以改,但是指针指向的值可以改
*p = 20;//正确
p = &b;//错误
const int* const p = &a;//const既修饰指针,又修饰常量
//指针的指向不可以改,指针指向的值也不可以改
7.6 指针和数组

作用:利用指针访问数组中的元素

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = arr;//指向数组的指针
for(int i = 0; i < 10 ; i++){
    cout << *(p + i) << " ";
}
cout <, endl;
7.7 指针和函数

作用 :利用指针作为函数参数,可以修改实参的值。

//swap函数
void swap(int* a, int* b){
    int temp = *a;
    *a = *b;
    *b = temp;
}//可以实现实参的对换
7.8 指针配合数组和函数案例
#include<iostream>
#include<stdio.h>
#include<stdlib.h>

using namespace std;

void bubbleSort(int* arr, int len){
    for(int i = 0; i < len; i++){
        for(int j = 0; j < len - i - 1; j++){
            if(arr[j] > arr[j+1]){
                int temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

void printArray(int* arr, int len){
    for(int i = 0; i < len; i++){
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main(){

    int arr[10] = {3, 4, 0, 5, 2, 8, 7, 6, 1, 9};

    int len = sizeof(arr) / sizeof(arr[0]);

    printArray(arr, len);
    bubbleSort(arr, len);
    printArray(arr, len);
    system("pause");
    return 0;
}

8. 结构体

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

8.2 结构体定义和使用

语法

struct 结构体名 {结构体成员列表};

通过结构体创建变量的方式有三种:

  • struct 结构体名 变量名
  • struct 结构体名 变量名 = {成员1值, 成员2值 …};
  • 定义结构体是顺便创建变量
//结构体定义
struct student{
    string name;
    int age;
    int score;
}studentA;
struct student studentB;
studentB.name = "张三";
struct student studentC = {"a", 18, 88};
//结构体变量创建的时候struct关键字可以省略
8.3 结构体数组

就把结构体类型当作一个新的数据类型来进行操作就可以了。

8.4 结构体指针
//结构体定义
struct student{
    string name;
    int age;
    int score;
}s;
int main(){
    student *p = &s;
    cout << s.name<<endl;//结构类型变量可以用.来访问属性
    cout << p->name <<endl;//对应指针类型的变量要用 -> 来访问属性
}
8.5 结构体嵌套结构体
8.6 结构体做函数参数
  • 值传递
  • 地址传递
8.7 结构体中const使用场景

作用:用const防止误操作

防止在进行地址传递的时候修改了实参的值

//结构体定义
struct student{
    string name;
    int age;
    int score;
}s;

//常量指针
void printStudent(const student* p){//这里const的意思是不能修改p指向的对象的属性值
	cout << p->name << endl;
}

9. 内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区的意义:

不同区域存放的数据,赋予不同的声明周期,给我们更大的灵活编程。

9.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:

代码区

​ 存放CPU执行时的机器指令

​ 代码区是共享的,共享的目的是,对于频繁被执行的程序,只需要在内存中有一份代码即可

​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区

​ 全局变量和静态变量存放在此

​ 全局区还包含了常量区,字符串常量和其他常量也存放在此

该区域的数据在程序结束后由操作系统释放

常量包括:字符串常量;const关键字修饰的常量,其中const修饰的全局变量在全局区,而修饰的局部变量并不在

9.2 程序运行后

栈区

​ 由编译器自动分配释放,存放函数的参数值,局部变量等

​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

堆区

​ 由程序员分配释放,若程序员不释放,程序结束由操作系统回收

​ 在C++中主要利用new关键字在堆区开辟内存

9.3 new关键字

释放时使用关键字delete

利用new创建的数据,会返回该数据对应的类型的指针

int* func(){
    int* p = new int(10);
    return p;
}

int main(){
    int *p = func();
    delete p;
    //在堆区创建一个数组
    int* arr = new int[10];
    delete[] arr;
}

10. 引用

10.1 引用的基本作用

作用:给变量起别名

语法:

int a = 10;
int &b = a;//b是别名
10.2 引用注意事项
  • 引用必须进行初始化
  • 引用在初始化后,不可以改变
10.3 引用作函数参数

作用 :函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

void swap(int& a, int& b){
    int temp = a;
    a = b;
    b = temp;
}

int main(){
    int a = 10;
    int b = 20;
    swap(a, b);
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    system("pause");
}
10.4 引用做函数函数返回值

作用:引用可以作为函数返回值

注意:不要返回局部变量的引用

用法:函数调用作为左值

//函数调用作为左值
int& test(){
    static int a = 10;
    return a;
}

int main(){
    int &temp = test();
    test() = 1000;//相当于将a和temp指代的对象的值修改为了1000
}
//如果函数的返回值是引用,这个函数调用可以作为左值
10.5 引用的本质

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

int a = 10;
int& ref = a;
//编译器自动转换为 int* const ref = &a;指针常量的指针指向不可以修改,也说明为什么引用不可修改
ref = 20;//内存变量ref是引用,自动转换为: *ref = 20;
10.6 常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

int& ref = 10;//错误,引用必须指向一块合法的内存区域
const int& ref = 10;//正确,编译器将代码修改为,int temp = 10;const int& ref = temp;

11. 函数提高

11.1 函数默认参数

在C++中,函数的形参列表是可以有默认值的

默认参数放在最后

如果函数的声明有默认参数,函数实现就不能有默认参数

11.2 函数占位参数

C++函数的形参列表可以有占位参数,调用函数的时必需填补该位置

语法:返回值类型 函数名(数据类型){}

void func(int a, int){
    cout << "null" << endl;
}

int main(){
    func(10, 10);
}
11.3 函数重载

作用:函数名可以重用,提高复用性

函数重载满足条件

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同,或者个数不同,或者顺序不同

注意:函数的返回值不同不能作为函数重载的条件

注意事项

  1. 引用作为重载的注意事项

    void func(int &a){
        cout << "func()" << endl;
    }
    void func(const int &a){
        cout << "func2()" <<endl;
    }
    
    int main(){
        int a = 10;
        func(a);//这里调用的是不带const的那个,a是一个可读可写的变量,更倾向于不带const的
        func(10);//这里调用的是带const的那个,因为 int &a = 10;是错误的
    }
    
  2. 函数重载遇到默认参数

    void func(int a, int b = 10){
        cout << 1 << endl;
    }
    void func(int a){
        cout << 2 << endl;
    }
    //上面两个函数在编译的时候不会出错
    int main(){
        func(10);//但是在执行这个调用的时候编译器会不知道执行哪个函数,所以会出错
    }
    

12. 类和对象

C++面向对象的三大特性:封装、多态、继承

C++认为万物皆对象,对象上有属性和行为

12.1 封装

封装的意义

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

权限

  1. public 共有权限 类内可以访问,类外也可以访问
  2. protected 保护权限 类内可以访问,类外不可以访问
  3. private 私有权限 类内可以访问,类外不可以访问

子类可以访问父类的保护内容,但不能访问父类的私有内容

struct的默认权限是公共权限

class的默认权限是私有权限

成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

可以将类的声明和实现放在不同的类中

//point.h
#pragma once
#include<iostream>

using namespace std;

class Point{
public:
    void setX(int x);
    void setY(int y);
    int getX();
    int getY();
private:
    int p_X;
    int p_Y;
}
//使用这个类的时候只要包含 #include "point.h"
//point.cpp
#include "point.h"
#include<iostream>

using namespace std;

void Point::setX(int x){
    p_X = x;
}

void Point::setY(int y){
    p_Y = y;
}

int Point::getX(){
    return p_X;
}

int Point::getY(){
    return p_Y;
}
12.2 对象的初始化和清理
12.2.1 构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题

​ 一个对象或者变量没有初始状态,对其使用后结果也是未知

​ 同样的使用完一个对象和变量,没有及时清理也会造成一定的安全问题

类的构造函数和析构函数用于完成上述工作,这两个函数将会被编译器自动调用,完成对象初始化和清理工作

编译器提供的构造函数和析构函数是空实现

  • 构造函数:主要作用在于创建对象时为对象成员属性赋值
  • 析构函数:主要作用在于对象销毁前系统自动调用

在这里插入图片描述

12.2.2 构造函数的分类及调用

两种分类方式:

​ 按参数分为:有参构造和无参构造

​ 按类型分为:普通构造和拷贝构造

三种调用方式:

​ 括号法:

​ 显示法

​ 隐式转换法

//拷贝构造函数
Person(const Person& p){
    age = p.age;
}

Person p1();//不会调用默认构造函数,编译器会以为这是一个函数声明

//显示法
Person p2 = Person(10);
Person(10);//匿名对象,当前行执行结束后,系统会立即回收匿名对象
//不要使用拷贝构造函数来初始化匿名对象
Person(p3);//编译器会认为是 Person p3;

//隐式转换法
Person p4 = 4;//等价于  Person p4 = Person(4);
12.2.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时通常有三种情况:

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
12.2.4 构造函数调用规则

在这里插入图片描述

12.2.5 深拷贝与浅拷贝

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

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

class Person{
private:
    int m_Age;
    int* m_Height;
    
    //拷贝构造函数
    Person(const Person & p){
        m_Age = p.m_Age;
        //m_Height = p.m_Height;编译器默认实现的浅拷贝操作
        m_Height = new int(*p.m_Height); //深拷贝操作
        //在堆区中新申请了一块内存,用于存放相关数据
    }
}
//析构代码
if(m_Height != NULL){
    delete m_Height;
    m_Height == NULL;
}
12.2.6 初始化列表

作用:

C++提供了初始化列表语法,用来初始化属性

class Person{
public:
    int m_A;
    int m_B;
    int m_C;
    
    //使用初始化列表初始化属性
    Person(int a, int b, int c): m_A(a), m_B(b), m_C(c) {}
}
12.2.7 类对象作为类成员

C++类中的成员可以是类一个类的成员

class A{
public:
    int m_age;
}
class B{
public:
	A a;
};
//创建B类的对象时,会先调用A中的构造函数,然后再调用B类的构造函数
//析构的顺序与构造相反
12.2.8 静态成员

静态成员就是在成员变量和成员函数前加上传剪子static,成为静态成员

静态成员变量要类内声明,类外进行初始化

静态成员分为:

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数\
    • 静态成员函数只能访问静态成员变量
//有两种访问方式
class Person{
public:
    static int m_B;
    static void func(){
        cout << "静态成员函数的调用" << endl;
    }
    //静态成员函数只能访问静态成员变量
    //静态成员函数也是有访问权限的,类外访问不到私有的静态成员函数
}
int Person::m_B = 0;//类外初始化

void test(){
    //1.通过对象来进行调用
    Person p;
    p.func();
    //2.通过类名来进行调用
    Person::func();
}
12.3 C++对象模型和this指针
12.3.1 成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上

空对象占用内存空间为1

C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置

每个空对象也应该有一个独一无二的内存地址

12.3.2 this指针概念

通过12.3.1我们知道C++中成员变量和成员函是分开存储的

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

那么问题是:这一块代码是如何区分是哪个对象调用自己的呢?

C++通过提供特殊的对象指针:this指针,解决上述问题:this指针指向被调用的成员函数所属的对象

this指针是隐含在每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员变量同名时,可以用this指针来进行区分
  • 在类的非静态成员函数中返回对象本身,可使用 return *this;
class Person{
public:
    int age;
    Person(int age){
        this -> age = age;
    }
}//用于区分同名变量
class Person{
public:
    int age;
    Person& PersonAddAge(const Person & p){
        //这里要返回引用,而不是值,如果是值的话相当于在返回的时候调用拷贝构造函数,链式之后的
        //处理都是在新对象上的,而不是在原来的p2
        this -> age += p.age;
        return *this;
    }
}

int main(){
    Person p1(10);
    Person p2(10);
    //链式编程思想
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    //在类的非静态成员中返回对象本身
}
12.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

if(this == NULL){
	return;
}//添加到用到this指针的函数中,防止空指针调用时程序崩溃
12.3.4 const修饰成员函数

常函数

  • 成员函数后加const我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加mutable关键字,在常函数中依然可以改变

常对象

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
class Person{
public:
    int m_A = 0;
    mutable int m_B = 0;
    //this指针的本质是一个指针常量,指针的指向是不可以修改的
    //Person * const this. this指针就相当于是前面的那种声明
    //如果函数声明时再加一个const,那么this指针指向的值也就不能修改了
    void showPerson() const{
        //m_A = 100;错误
        m_B = 100;
    }
}

const Person p;//常对象
p.m_A = 100;//错误
P.m_B = 100;//正确
//常对象只能调用常函数
p.showPerson();
12.4 友元

在这里插入图片描述

12.4.1 全局函数做友元
class Building{
   	//声明友元
    friend void goodGay(Building *building);
private:
    string bedroom;
};

//本来在类外是不能访问类的私有属性的
//但是将全局函数声明为类的友元有以后,就可以访问类的私有属性了
void goodGay(Building *building){
    cout << building -> bedroom <<endl;
}
12.4.2 类做友元
friend class goodGay;
12.4.3 成员函数做友元
friend void goodGay::visit();
12.5 运算符重载
12.5.1 加号运算符重载

可以通过成员函数来进行重载+,也可以通过全局函数来重载 +

运算符重载也可以发生函数重载

对于内置数据类型不能进行运算符重载

class Person{
public:
    int m_A;
    int m_B;
    //通过成员函数来重载加号
    Person operator+(const Person & p) const;
}

Person Person::operator+(const Person & p) const{
    Person temp;
    temp.m_A = this -> m_A + p.m_A;
    temp.m_B = this -> m_B + p.m_B;
    return temp;
}

//类外调用
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_B = 10;
p2.m_A = 10;
Person p3 = p1 + p2;
//成员函数的本质调用
Person p3 = p1.operator(p2);

//通过全局函数来重载加号
Person operator+(const Person & p1, const Person & p2){
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp
}

//调用
Person p3 = p1 + p2;
//全局函数的本质调用
Person p3 = operator+(p1, p2);
12.5.2 左移运算符重载

作用:可以输出自定义数据类型

通常不会利用成员函数来重载左移运算符,因为无法实现cout在左侧

//只能利用全局函数重载左移运算符
class Person{
    friend ostream& operator<<(ostream &cout, Person &p);
public:
    int m_A;
    void setB(int b){
        this -> m_B = b;
    }
private:
    int m_B;
}

//重载运算符
//如果要输出其中的私有变量的话,就要将这个函数添加为友元
ostream& operator<<(ostream &cout, Person &p){
    cout << "m_A = " << m_A << "m_B = " << m_B << endl;
    return cout;
}

//调用
Person p;
p.m_A = 10;
p.setB(10);
cout << p << endl;
12.5.3 递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

递增包括前置递增和后置递增

前置递增返回的是引用,后置递增返回的是值

class MyInteger{
    friend ostream& operator<<(ostream &cout, MyInteger a);
public:
    MyInteger(){
        this -> num = 0;
    }

    //实现前置++运算符重载
    //返回引用是为了一直对一个数据进行递增
    MyInteger& operator++(){
        this -> num++;
        return *this;
    }

    //实现后置++运算符重载
    //这里要使用占位符来区分前置递增和后置递增
    //后置递增不返回引用,因为不能返回局部对象的引用
    MyInteger operator++(int){
        //先记录当时结果
        MyInteger temp = *this;
        //再执行++操作
        num++;
        //最后返回记录的结果
        return temp;
    }
private:
    int num;
};

ostream& operator<<(ostream &cout, MyInteger a){
    cout << a.num;
    return cout;
}

void test(){
    MyInteger a;
    //测试前置递增
    cout << ++(++a) <<endl;
    //测试后置递增
    cout << a++ <<endl;
    cout << a <<endl;
}

int main(){
    test();
    system("pause");
}
12.5.4 赋值运算符重载

在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<stdlib.h>

using namespace std;

class Person{
public:
    //构造函数
    Person(int age){
        m_Age = new int(age);
    }

    //析构函数
    ~Person(){
        if(m_Age != NULL){
            delete m_Age;
            m_Age = NULL;
        }
    }

    //默认提供的=的重载是值拷贝(浅拷贝操作)
    //要用深拷贝重新实现赋值运算符的重载
    Person& operator=(Person &p){
        //编译器默认提供的是浅拷贝操作
        //this -> m_Age = p.m_Age;

        //先释放当前m_Age中的内容,再进行深拷贝操作
        if(this -> m_Age != NULL){
            delete this -> m_Age;
            this -> m_Age = NULL;
        }

        //执行深拷贝操作
        this -> m_Age = new int(*p.m_Age);
        return *this;
        //赋值运算符也要支持链式编程
    }

    int *m_Age;
};

void test(){
    Person p1(18);
    Person p2(20);
    Person p3(22);
    p3 = p1 = p2;
    cout << "p1的年龄为: " << *p1.m_Age <<endl;
    cout << "p2的年龄为: " << *p2.m_Age <<endl;
    cout << "p3的年龄为: " << *p3.m_Age <<endl;
}

int main(){
    test();
    system("pause");
}
12.5.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型进行对比操作

#include<iostream>
#include<stdio.h>
#include<stdlib.h>

using namespace std;

class Person{
public:
    int m_Age;

    Person(){}

    Person(int age){
        m_Age = age;
    }
    
    //重载关系运算符
    bool operator==(const Person &p){
        if(this -> m_Age == p.m_Age)
            return true;
        return false;
    }

    bool operator!=(const Person &p){
        if(this -> m_Age != p.m_Age)
            return true;
        return false;
    }

    bool operator>=(const Person &p){
        if(this -> m_Age >= p.m_Age)
            return true;
        return false;
    }

    bool operator<=(const Person &p){
        if(this -> m_Age <= p.m_Age)
            return true;
        return false;
    }

    bool operator>(const Person &p){
        if(this -> m_Age > p.m_Age)
            return true;
        return false;
    }

    bool operator<(const Person &p){
        if(this -> m_Age < p.m_Age)
            return true;
        return false;
    }
};

void test(){
    Person p1(18);
    Person p2(20);
    if(p1 == p2){
        cout << "相等" <<endl;
    }
    if(p1 != p2){
        cout << "不相等" << endl;
    }
}

int main(){
    test();
    system("pause");
}
12.5.6 函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
class MyPrint{
public:
    void operator()(string test){
        cout << test << endl;
    }
};

void test(){
    MyPrint myprint;
    myprint("hello world");
    //非常像一个函数调用
    
    //匿名函数对象,MyPrint()是一个匿名对象,然后又重载了()
    MyPrint()("hello");
}

int main(){
    test();
    system("pause");
}
12.6 继承

继承是面向对象三大特性之一

在这里插入图片描述

//写法
class A{
    
};
class B: public A{
    
};
//继承的好处:减少重复的代码
12.6.2 继承方式

继承方式一共有3种:

  • 公共继承
  • 保护继承
  • 私有继承

在这里插入图片描述

12.6.3 继承对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

class Base{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son: public Base{
public:
    int m_D;
};

void test(){
    Son son;
    cout << "sizeof son = " << sizeof(son) << endl;
    //输出结果为16
    //说明所有非静态属性都在子类对象中存放,但是父类私有属性被设置成了不可见
}

int main(){
    test();
    system("pause");
}
12.6.4 继承中构造和析构的顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

构造顺序:先父类后子类

析构顺序正好相反

栈区先进后出?

12.6.5 继承中同名成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
class Base{
public:
    int m_A;

    Base(){
        m_A = 100;
    }
};

class Son: public Base{
public:
    int m_A;

    Son(){
        m_A = 200;
    }
};

void test(){
    Son son;
    cout << "Son 中 m_A = " << son.m_A << endl;
    cout << "Base 中 m_A = " << son.Base::m_A <<endl;//访问父类中同名元素需要添加作用域
}

int main(){
    test();
    system("pause");
}
12.6.6 继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理防止一致

//父类同名静态属性通过子类类名访问
//第一个::表示通过类名的方式来进行访问,第二个::表示作用域
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
12.6.7 多继承语法

C++允许一个类继承多个类

多继承可能会引发父类中有同名成员出现,需要加作用域加以区分

C++实际开发中并不建议使用多继承

多继承中间用逗号隔开

12.6.8 菱形继承

在这里插入图片描述
在这里插入图片描述

菱形继承导致数据有两份,资源浪费

底层实现回头再看视频

class A{
public:
    int m_A;
};

//采用虚继承的方式可以解决菱形继承中数据冗余的问题
class B: virtual public A{};

class C: virtual public A{};

class D: public B, public C{};

void test(){

    D d;
    //加上虚继承以后,下面三种方式访问的都是同一块内存
    d.B::m_A = 10;
    d.C::m_A = 20;
    cout << d.m_A << endl;

}

int main(){
    test();
    system("pause");
}
12.7 多态
12.7.1 多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

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

静态多态和动态多态的区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal{
public:
    void speak(){
        cout << "动物在说话" << endl;
    }
};

class Cat: public Animal{
public:
    void speak(){
        cout << "猫在说话" << endl;
    }
};

void doSpeak(Animal &animal){
    animal.speak();
}

void test(){

   Cat cat;
   doSpeak(cat);
   //这时候输出的结果是“动物在说话”,因为静态多态是早绑定,在编译阶段就确定了函数地址
   //在编译阶段,传入的参数类型是Animal,所以会执行Animal类中的speak函数

}

int main(){
    test();
    system("pause");
}
//虚函数,地址晚绑定
public:
    virtual void speak(){
        cout << "动物在说话" << endl;
    }
};
public:
 	virtual void speak(){
        cout << "猫在说话" << endl;
    }
//之后的代码不变,这种情况下就是动态多态,地址晚绑定
//动态多态满足条件
//要有继承关系
//子类要重写父类的虚函数

//动态多态实现
//父类的指针或引用指向子类

重写:函数返回值类型,函数名,参数列表要完全相同

12.7.2 多态的原理剖析

在这里插入图片描述

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

开闭原则:对扩展进行开放,对修改进行闭合

12.7.3 纯虚函数和抽象类

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

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

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

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
12.7.4 虚析构和纯虚析构

在这里插入图片描述

问题:在动态多态里,是将父类的指针指向了子类的对象,所以在delete父类指针的时候,并不会调用子类的析构函数。

解决方法:使用虚析构或者纯虚析构

虚析构和纯虚析构函数都需要实现

可以在类外使用 类名::~类名(){}来实现

在这里插入图片描述

最后组装电脑的例子,自己抽时间还是要回顾一下

13.文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化

C++中对文件操作需要包含头文件**#include**

文件类型分为两种:

  • 文本文件:文本文件以ASCII码形式存储在计算机中
  • 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作
13.1 文本文件
13.1.1 写文件

在这里插入图片描述

在这里插入图片描述

文件打开方式:

在这里插入图片描述

在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<fstream>

using namespace std;

void test(){
    //1.包含头文件 fstream

    //2.创建文件流对象,例如要写入文件
    ofstream ofs;

    //3.打开文件
    ofs.open("test.txt", ios::out);
    //默认是放在与cpp文件同目录下

    //4.写内容
    ofs << "姓名:张三" << endl;
    ofs << "性别:男" << endl;
    ofs << "年龄:20" << endl;
    
    //5.关闭文件
    ofs.close();
}

int main(){
    test();
    system("pause");
}
13.1.2 读文件

读文件与写文件步骤类似,但是读取方式相对比较多

在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<fstream>

using namespace std;

//文本文件 读文件
void test(){
    //1.包含头文件 fstream

    //2.创建文件流对象,例如要写入文件
    ifstream ifs;

    //3.打开文件,并判断文件是否打开成功
    ifs.open("test.txt", ios::in);
    //默认是放在与cpp文件同目录下
    if(!ifs.is_open()){
        cout << "文件打开失败" << endl;
        return;
    }
    //4.读内容
    //一个文件被打开后,只能读取一次
    
    //第一种,将文件中的内容一行行的读取到buff中
    char buff[1024] = {0};
    while(ifs >> buff){
        cout << buff << endl;
    }

    //第二种,
    char buff2[1024] = {0};
    while(ifs.getline(buff2, sizeof(buff2))){
        cout << buff2 <<endl;
    }

    //第三种
    string buff3;
    while(getline(ifs, buff3)){
        cout << buff3 << endl;
    }

    //第四种
    char c;
    while((c = ifs.get()) != EOF){
        cout << c;
        //一个一个地读取字符,不常用
    }

    //5.关闭文件
    ifs.close();
}

int main(){
    test();
    system("pause");
}
13.2 二进制文件
13.2.1 写文件

二进制方式写文件主要利用流对象成员函数write

在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<fstream>

using namespace std;

class Person{
public:
    char m_Name[50];
    int m_Age;
};

//二进制文件 写文件
void test(){

    //1.包含头文件

    //2.创建文件流对象
    ofstream ofs;

    //3.打开文件
    ofs.open("person.txt", ios::out | ios::binary);

    //4.写文件
    Person p = {"张三", 18};
    ofs.write((const char*)&p, sizeof(p));

    //5.关闭文件
    ofs.close();
}

int main(){
    test();
    system("pause");
}
13.2.2 读文件

在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<fstream>

using namespace std;

class Person{
public:
    char m_Name[50];
    int m_Age;
};

//二进制文件 写文件
void test(){

    //1.包含头文件

    //2.创建文件流对象
    ifstream ifs;

    //3.打开文件
    ifs.open("person.txt", ios::out | ios::binary);
    if(!ifs.is_open()){
        cout << "文件打开失败" << endl;
        return;
    }

    //4.写文件
    Person p;
    ifs.read((char*)&p, sizeof(Person));
    //重点
    cout << "姓名: " << p.m_Name << " " << "年龄: " << p.m_Age << endl;

    //5.关闭文件
    ifs.close();
}

int main(){
    test();
    system("pause");
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值