C++知识点总结

一、C++简介

        1、c++的特点:

        1、在支持C语言的基础上,全面支持面向对象编程

        2、编程领域广泛,功能强大

        3、C++语言标准一直在保持更新

        4、支持底层操作的面向对象编程语言

        5、在面向对象编程语言中执行效率高

        2、面向过程与面向对象的区别

        面向过程是以“我想怎么解决”为核心;面向对象是以“我想让谁来解决这个问题”为核心。

        3、开发环境

        QtCreator

二、从C到C++

1、引用

1.1 概念

        引用类似于指针的平替,在所有面向对象的编程语言中都会被使用。引用相当于对某一目标变量起别名。

操作引用与操作原变量完全一样。

【问题】指针与引用的区别

        指针是一个变量,存储的是一个地址;引用变量是引用别名,本质跟原变量是同一个东西。

        引用必须被初始化且不能为NULL;指针声明时可以先不初始化且可以初始化为NULL

        引用初始化后不可再被改变,指针则可以改变指向的对象

        指针的大小是指针的大小,引用的大小是原变量的大小

1.2 引用的性质

        1、可以改变引用的值但是不能再次成为其他变量的引用

        2、声明引用时必须初始化,不能初始化为NULL

        3、声明引用时,初始化的值可以是纯数值,但此时需要用coust修饰,表示这是一个常量引用,此时引用的值不可被改变

2、赋值

        在C++中新增了一下赋值方法:

        int a (10);//相当于 int a=10,只能用于初始化

        int b (a);// int b=a

        int c (a+b);// int c=a+b

        double b = 3.14;

        int b1 = b;

        cout << b1 << endl; // 3 数据窄化

3、键盘输入 

可以使用cin把用户在命令行中的内容赋值到变量中。

cin和cout一样,都属于头文件iostream中的标准输入输出流,同时如果cin输入的字符串需要包含空格,则可以使用下面的方式:

#include <iostream>

using namespace std;


int main()
{
    // C++的字符串是string
    string str;

    cout << "请输入字符串,可以包含空格,输入完成后点击回车" << endl;
    getline(cin,str);   // 第二个参数只能是string类型

    cout << str << endl;

    return 0;
}

4、string字符串类

        string不是C++的基本数据类型,它是一个C++标准库中的字符串类,使用时需要引入对应的头文件,#include<string>,而不是string.h.

        string在绝大多数情况下,可以代替C语言中字符串,不必担心内存是否足够字符串长度等等。其中内部还包含了很多字符串处理函数,可以完成各种情况下的字符串处理功能。

        string和C语言相同,字符串编码使用ASCII编码,不支持中文。

#include <iostream>

using namespace std;


int main()
{
    string str = "helloworld";

    cout << str << endl;
    cout << str.size() << endl; // 10
    cout << str.length() << endl;   // 10

    cout << str[1] << endl; // e
    cout << str.at(1) << endl;  // e
    return 0;
}

at函数会在你输入要检索字符串大小超出原本大小时发出警告,程序运行停止。

        string类支持多种遍历方式

  • 普通循环(以for循环为主)
  • C++11:for each循环
#include <iostream>

using namespace std;


int main()
{
    string str = "helloworld";

    // 以for循环的方式进行输出字符串
    for(int i = 0; i < str.length(); i++)
    {
        cout << str.at(i);
    }
    cout << endl;

    // 以 for each的方式进行循环遍历字符串
    for(char i:str)
    {
       cout << i;
    }

    return 0;
}

        字符串与数字转换

#include <iostream>
#include <sstream> // 字符串流

using namespace std;

int main()
{
    string s = "123";
//    int i = s; 错误

    // string → int
    istringstream iss(s);
    int i;
    iss >> i;
    cout << i << endl;    // 123

    // int → string
//    string s2 = i; 错误
    stringstream ss;
    ss << i;
    string s2 = ss.str();
    cout << s2 << endl;

    return 0;
}

5、函数

5.1 内联函数

内联函数用于取代C语言中宏定义的函数,内联函数的正确使用可以提升程序的执行效率。内联函数在编译的时候,直接把函数体展开到主函数中编译,在运行期间可以减少调用的开销。

通常将具有以下性质的函数写为内联函数:

  • 代码长度5行以内
  • 不包含复杂的控制语句
  • 频繁被调用

关键字:inline

#include <iostream>

using namespace std;

// 内联函数
inline void pint_string(string str)
{
    cout << str << endl;
}

int main()
{
    pint_string("hello world");

    return 0;
}

        即使加上了内联函数关键字inline 具体是不是内联函数依旧由编译器决定

5.2 函数重载 overload

        函数重载要求函数名称相同但是参数不同(包括类型、数量、前后顺序)。与返回值等其他因素无关。

#include <iostream>

using namespace std;

void print_show(int i)
{
    cout << "调用了int重载"<< i << endl;
}

void print_show(float i)
{
    cout << "调用了float重载"<< i << endl;
}

void print_show(double i)
{
    cout << "调用了double重载"<< i << endl;
}

void print_show(string str)
{
    cout << "调用了string重载"<< str << endl;
}

void print_show()
{
    cout << "调用了无参int重载"<< endl;
}

int main()
{
    print_show(1);

    return 0;
}

5.3 哑元函数

        该函数的参数只有类型,没有名称

        作用一:哑元函数用来区分函数重载。

        作用二:运算符重载中用到。

#include <iostream>

using namespace std;

// 哑元函数
void print_show(int)
{
    cout << "调用了int的哑元函数" << endl;
}

int main()
{
    print_show(56);

    return 0;
}


三、面向对象基础

1、类与对象

1.1概念

        类:类是一个抽象的概念,类是对象的特点

        对象:根据类创建的实体

1.2 类的内容

【例子】以手机为例

规定手机可以播放音乐、运行游戏、打电话、手机有品牌、型号、重量等属性

#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
public:     // 权限:public最开放的权限
    string brand;   // 品牌
    string model;   // 型号
    int weight; // 重量

    void play_music()
    {
        cout << "只因你太美,哒哒哒" << endl;
    }

    void run_game()
    {
        cout << "原神启动、赛尔号、奇迹暖暖、三国杀、够级、保皇、金铲铲、咸鱼之王" << endl;
    }

    void call()
    {
        cout << "坤哥您好~~~" << endl;
    }

};


int main()
{

    return 0;
}

1.3对象的创建

栈内存对象:对象所在的{}执行完毕后,自动销毁

#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
public:     // 权限:public最开放的权限
    string brand;   // 品牌
    string model;   // 型号
    int weight; // 重量

    void play_music()
    {
        cout << "只因你太美,哒哒哒" << endl;
    }

    void run_game()
    {
        cout << "原神启动、赛尔号、奇迹暖暖、三国杀、够级、保皇、金铲铲、咸鱼之王" << endl;
    }

    void call()
    {
        cout << "坤哥您好~~~" << endl;
    }

};


int main()
{
    MobilePhone mp; // 栈内存对象
    mp.brand = "华为";
    mp.model = "mate70pro Max";
    mp.weight = 2000;

    cout << mp.brand << mp.model << mp.weight << endl;

    mp.play_music();
    mp.run_game();
    mp.call();

    return 0;
}

        堆内存对象:必须使用new创建,使用指针保存,如果不使用delete关键字销毁,则内存堆像会出现内存泄漏。堆内存对象调用成员时,使用的是->

#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
public:     // 权限:public最开放的权限
    string brand;   // 品牌
    string model;   // 型号
    int weight; // 重量

    void play_music()
    {
        cout << "只因你太美,哒哒哒" << endl;
    }

    void run_game()
    {
        cout << "原神启动、赛尔号、奇迹暖暖、三国杀、够级、保皇、金铲铲、咸鱼之王" << endl;
    }

    void call()
    {
        cout << "坤哥您好~~~" << endl;
    }

};


int main()
{
    MobilePhone *mp = new MobilePhone;  // 堆内存对象
    mp->brand = "小米";
    mp->model = "14 ultra";
    mp->weight = 1300;

    cout << mp->brand << mp->model << mp->weight << endl;

    mp->play_music();
    mp->run_game();
    mp->call();

    delete mp;  // 手动销毁
    mp = NULL;  // 指向空,防止野指针

    return 0;
}

2、封装

        封装指的是,将类的一些属性和细节隐藏,重新提供外部的访问接口。封装可以提升代码的安全性,并且可以让程序员更关注于上层架构而非内部细节。(依旧用上一个手机的例子)

#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
private:        // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string model;   // 型号
    int weight = 200; // 重量

public:     // 权限:public最开放的权限
    // get读函数
    string get_brand()
    {
        return brand;
    }

    // set写函数
    void set_brand(string b)
    {
        brand = b;
    }

    // get读函数
    string get_model()
    {
        return model;
    }

    // set写函数
    void set_model(string b)
    {
        model = b;
    }

    // get读函数
    int get_weight()
    {
        return weight;
    }
};


int main()
{
    MobilePhone *mp = new MobilePhone;  // 堆内存对象
    mp->set_brand("老王手机");
    mp->set_model("隔壁");

    cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;

    delete mp;
    return 0;
}

3、构造函数

3.1 基本使用

        构造函数是一种特殊的成员函数,用于创建对象时初始化,创建对象时必须直接或者间接调用当前类的任意一个构造函数。写法上有以下要求:

  • 函数名成与类名完全相同
  • 构造函数不写返回值类型,不写返回值
  • 如果程序员不手动编写构造函数,编译器会自动添加一个默认无参的构造函数。(手动编写构造函数后,编译器则不会添加无参构造函数)
  • 构造函数也同样支持函数重载、参数默认值
#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
private:        // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string model;   // 型号
    int weight; // 重量

public:     // 权限:public最开放的权限

//    MobilePhone()
//    {
//        brand = "8848";
//        model = "M6巅峰版藏地棕牛皮";
//        weight = 512;
//    }
    MobilePhone(string b,string m,int w)
    {
        brand = b;
        model = m;
        weight = w;
    }
    // get读函数
    string get_brand()
    {
        return brand;
    }

    // get读函数
    string get_model()
    {
        return model;
    }

    // get读函数
    int get_weight()
    {
        return weight;
    }
};


int main()
{
    // 堆内存对象
    MobilePhone *mp = new MobilePhone("魅族","不知道",300);  // 堆内存对象

    cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;

    delete mp;

    // 栈内存对象
    MobilePhone mp1("8848","M6巅峰版藏地棕牛皮",512);
    cout << mp1.get_brand() << mp1.get_model() << mp1.get_weight() << endl;
    return 0;
}

3.2 初始化构造列表

        初始化列表是一种更简单的给成员变量赋值的写法。

        当构造函数的局部变量(形参)与成员变量重名时,除了使用后面学习的this指针的方式外,也可以使用构造初始化列表区分。

#include <iostream>

using namespace std;

// 帕斯卡命名法(大驼峰命名法)
// 每个单词的首字母大写
class MobilePhone
{
private:        // 私有权限,最封闭的权限,只能在类内访问
    string brand;   // 品牌
    string model;   // 型号
    int weight; // 重量

public:     // 权限:public最开放的权限


    // 无参构造函数,构造初始化列表
    MobilePhone()
        :brand("8848"),model("M6巅峰版藏地棕牛皮"),weight(520){}

    // 构造初始化列表
    MobilePhone(string b,string m,int w)
        :brand(b),model(m),weight(w){}

    // get读函数
    string get_brand()
    {
        return brand;
    }

    // get读函数
    string get_model()
    {
        return model;
    }

    // get读函数
    int get_weight()
    {
        return weight;
    }
};

int main()
{
    // 栈内存对象
    MobilePhone mp1;
    cout << mp1.get_brand() << mp1.get_model() << mp1.get_weight() << endl;

    return 0;
}

3.3 隐式调用与显式调用

        构造函数的调用可以分为显式调用隐式调用

        显式调用指的是在创建对象时手写构造函数的名称及参数列表。

        隐式调用指的是在创建对象时不写构造函数的名称与参数列表,编译器会尝试调用对应参数的构造函数。

#include <iostream>

using namespace std;

class Student
{
private:
    int age;
public:
    Student(int a):age(a)
    {
        cout << "构造函数" << endl;
    }
    Student()
    {
        cout << "无参构造函数" << endl;
    }

    int get_age()
    {
        return age;
    }
};

int main()
{
    Student s1(12); // 显式调用
    cout << s1.get_age() << endl;

    Student s3 = Student(14);   // 显示调用
    cout << s3.get_age() << endl;

//    Student s4 = 15;    // 隐式调用
//    cout << s4.get_age() << endl;

    Student *s2 = new Student(13);  // 显式调用
    cout << s2->get_age() << endl;

    Student s6;   // 默认调用无参构造函数

    return 0;
}

3.4 拷贝构造函数

3.4.1 概念

        当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数实现

#include <iostream>

using namespace std;

class Student
{
private:
    int age;
public:
    Student(int a):age(a)
    {
        cout << "构造函数" << endl;
    }
    Student()
    {
        cout << "无参构造函数" << endl;
    }

    // 手写默认添加的拷贝构造函数
    Student(const Student &st)
    {
        cout << "手写的拷贝构造函数被调用了" << endl;
        age = st.age;
    }

    int get_age()
    {
        return age;
    }
};

int main()
{
    Student s1(12);
    cout << s1.get_age() << endl;   // 12

    Student s2(s1); // 调用拷贝构造函数
    cout << s2.get_age() << endl;   // 12

    return 0;
}

【思考】:拷贝构造函数是否存在隐患?

        存在,当成员变量存在指针类型时,默认的拷贝构造函数会导致两个对象的成员变量指向同一处,不符合面向对象的设计规范的。这种现象被称为”浅拷贝“。

3.4.2 浅拷贝

        所有的对象公用同一个地址,当改变一个对象的内容时,所有的对象内容都会被改变

#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char *name;
public:
    Dog(char *n)
    {
        name = n;
    }

    void show_name()
    {
        cout << name << endl;
    }
};

int main()
{
    char arr[20] = "旺财";
    Dog d1(arr);
    Dog d2(d1); // 拷贝构造函数

    strcpy(arr,"大黄");   // 浅拷贝更改外部内存,对象内的数据,也被更改。因为操作的是同一块内存
    d1.show_name();
    d2.show_name();

    return 0;
}

        这种情况必需手动编写拷贝构造函数,使每次赋值都创建一个新的副本,从而让每个对象单独持有自己的成员变量,这种方式也称作深拷贝。

3.4.3 深拷贝

#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char *name;
public:
    Dog(char *n)
    {
        name = new char[20];
        strcpy(name,n);
    }

    Dog(const Dog &d)
    {
        name = new char[20];
        strcpy(name,d.name);
    }

    void show_name()
    {
        cout << name << endl;
    }
};

int main()
{
    char arr[20] = "旺财";
    Dog d1(arr);
    Dog d2(d1); // 拷贝构造函数

    strcpy(arr,"大黄");   // 浅拷贝更改外部内存,对象内的数据,也被更改。因为操作的是同一块内存

    d1.show_name(); // 旺财
    d2.show_name(); // 旺财

    return 0;
}
3.4.4 析构函数

        析构函数是与构造函数完全对立的,用于销毁对象释放资源

构造函数

析构函数

创建对象时手动调用

当对象销毁时,自动调用

函数名称是类名

函数名称~类名

构造函数可以重载

析构函数没有参数,不能重载

用于创建对象时并初始化

用于销毁对象释放资源

有返回值但是不写,返回值时新创建的对象

没有返回值

#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char *name;
public:
    Dog(char *n)
    {
        name = new char[20];
        strcpy(name,n);
    }

    Dog(const Dog &d)
    {
        name = new char[20];
        strcpy(name,d.name);
    }

    void show_name()
    {
        cout << name << endl;
    }

    ~Dog()
    {
        cout << "析构函数被调用了" << endl;
        delete []name;
    }

};

int main()
{
    char arr[20] = "旺财";
    Dog d1(arr);
    Dog d2(d1); // 拷贝构造函数

    strcpy(arr,"大黄");   // 浅拷贝更改外部内存,对象内的数据,也被更改。因为操作的是同一块内存

    d1.show_name(); // 旺财
    d2.show_name(); // 旺财

    return 0;
}

【思考】:

如果类中不写任何权限,默认什么权限?

        私有权限

如果不手动添加任何函数,编译器会自动添加哪些函数?

        无参构造函数、拷贝构造函数、析构函数

如果添加了无参构造函数,默认的无参构造函数还会添加嘛?

        不会

如果添加了任意一个构造函数,默认无参构造函数会添加嘛?

        不会

如果添加了拷贝构造函数,默认无参构造函数会添加嘛?

        不会

如果添加了默认无参构造函数,拷贝构造函数还会帮你添加嘛?

        会

如果添加了有参构造函数,拷贝构造函数还会帮你添加嘛?

        会

3.5 作用域限定符:: 

3.5.1 名字空间
#include <iostream>

using namespace std;

namespace my_space
{
    int a = 3;
    int b = 4;
}


int a = 2;

using namespace my_space;


int main()
{
    int a = 1;
    cout << a << endl;  // 1 就近原则
    cout << ::a << endl;    // ::匿名名字空间。2
    cout << my_space::a << endl;    // 3
    cout << b << endl;  // 4

    return 0;
}
3.5.2 类内声明,类外定义
#include <iostream>

using namespace std;

class Demo
{
public:
    // 类内声明
    Demo();
    void test(string str);
};

// 类外定义
Demo::Demo()
{
    cout << "构造函数" << endl;
}

void Demo::test(string str)
{
    cout << "str= " << str << endl;
}


int main()
{
    Demo d;
    d.test("hello");

    return 0;
}

3.6 this指针

3.6.1 概念

        this指针指向的是当前类对象的首地址

#include <iostream>

using namespace std;

class Test
{
public:
    void test_this()
    {
        cout << this << endl;
    }
};

int main()
{
    Test t1;
    cout << &t1 << endl;    // 0x61fe8f
    t1.test_this();         // 0x61fe8f

    Test t2;
    cout << &t2 << endl;    // 0x61fe8e
    t2.test_this();         // 0x61fe8e

    Test *t3 = new Test;
    cout << t3 << endl;     // 0x732808
    t3->test_this();        // 0x732808

    delete t3;
    return 0;
}
3.6.2 功能
3.6.2.1 类内调用成员

        成员(变量+函数)必须由对象调用,类内调用成员都依赖this指针,通常由编译器自动添加

#include <iostream>

using namespace std;

class Test
{
private:
    string name;
public:
    Test(string n)
    {
        // 编译器默认添加this指针指向当前的对象,调用成员
        this->name = n;
    }

    string get_name()
    {
        // 编译器默认添加this指针指向当前的对象,调用成员
        return this->name;
    }

    void test_this()
    {
        cout << this << endl;
    }
};

int main()
{
    Test t1("zhangsan");
    cout << t1.get_name() << endl;
    return 0;
}
3.6.2.2 区别重名的成员变量与局部变量
#include <iostream>

using namespace std;

class Test
{
private:
    string name;
public:
    Test(string name)/*:name(name)*/    // 构造初始化列表也可以区分
    {
        // 通过this指针在函数体中进行区分
        this->name = name;
    }

    string get_name()
    {
        // 编译器默认添加this指针指向当前的对象,调用成员
        return this->name;
    }

    void test_this()
    {
        cout << this << endl;
    }
};

int main()
{
    Test t1("zhangsan");
    cout << t1.get_name() << endl;
    return 0;
}

6.2.3 链式调用

支持链式调用的成员函数特点:

  1. 当一个成员函数的返回值是当前类型的引用时,往往这个函数就支持链式调用。
  2. return 后面是*this
#include <iostream>

using namespace std;

class Test
{
private:
    int val = 0;
public:
    Test& add(int i)
    {
        val += i;   // val = val + i;
        return *this;   // this是一个指针,取内容返回当前对象
    }

    int get_val()
    {
        return val;
    }

};


int main()
{
    Test t1;
    t1.add(1);
    t1.add(2);
    t1.add(100);
    cout << t1.get_val() << endl;   // 103

    // 链式调用
    Test t2;
    cout << t2.add(2).add(32).add(200).get_val() << endl;   // 234
    cout << t2.get_val() << endl;   // 234
    return 0;
}

7、static关键字

7.1 静态局部变量

        使用static修饰局部变量,这样的变量就是静态局部变量。

        静态局部变量在第一次调用时创建,直到程序结束后销毁,同一个类的所有对象共用这一份静态局部变量。

#include <iostream>

using namespace std;

class Test
{
public:
    void func()
    {
        int a = 1;
        static int b = 1;   // 静态局部变量
        cout << "a = " << ++a << " " << &a << endl;
        cout << "b = " << ++b << " " << &b << endl;
    }
};

int main()
{
    Test t1;
    t1.func();
    t1.func();
    cout << "--------------" << endl;

    Test t2;
    t2.func();


    Test t3;
    t3.func();

    return 0;
}

7.2 静态成员变量

        使用static修饰成员变量,这样的变量就是静态成员变量。

        静态成员变量需要类内声明。类外初始化。

        一个类的所有对象共用一份静态成员变量,虽然静态成员变量可以使用对象调用,但是更建议使用类名直接调用。所以静态成员变量可以脱离对象使用,在程序开始运行时就开辟内存空间,直到程序运行结束后销毁。

        更推荐直接使用类名调用。代码的可读性更好。

#include <iostream>

using namespace std;

class Test
{
public:
    int a = 1;
//    static int b = 2; // 错误,静态成员变量需要类内声明。类外初始化
    static int b;   // 类内声明
};

// 类外初始化
int Test::b = 1;

int main()
{
    cout << Test::b << " " << &Test::b << endl; // 1 0x403004
    Test t1;
    cout << t1.a++ << " " << &t1.a << endl; // 1 0x61fe8c
    cout << t1.b++ << " " << &t1.b << endl; // 1 0x403004

    cout << t1.a++ << " " << &t1.a << endl; // 2 0x61fe8c
    cout << t1.b++ << " " << &t1.b << endl; // 2 0x403004

    cout << "------------------" << endl;

    Test t2;
    cout << t2.a++ << " " << &t2.a << endl; // 1 0x61fe88
    cout << t2.b++ << " " << &t2.b << endl; // 3 0x403004

    cout << Test::b << " " << &Test::b << endl;// 4 0x403004

    return 0;
7.2 静态成员函数

        使用static修饰成员函数,这样的函数就是静态成员函数。

        与静态成员变量相似的有:

  • 都可以通过类名直接调用,也可以通过对象调用。(推荐使用类名直接调用)
  • 可以脱离对象使用
  • 如果再静态成员函数中调用非静态成员。可以通过参数将对象传递进来,因为静态成员函数没有this指针。
  • 也可以通过再静态成员函数中,创建新的对象进行调用。

        因此,静态成员函数没有this指针,不能再静态成员函数中调用同类其他非静态成员,但是静态成员函数可以调用静态成员。

#include <iostream>

using namespace std;

class Test
{
public:
    void func0()
    {
//        func1(); 
        cout << "非静态成员函数" << endl;
    }

    static void func1()
    {
//        func0(); // 错误,静态成员函数,不能调用非静态成员
        cout << "静态成员函数" << endl;
    }

    static void func2()
    {
        cout << "静态成员函数2" << endl;
    }
};


int main()
{
//    Test::func1();
    Test t1;
//    t1.func0();
    t1.func1();
//    t1.func2();

    return 0;
}

8.const关键字

8.1 const修饰成员函数

        cosnt修饰的成员函数,表示常成员函数。

特性如下:

  • 可以调用成员变量,但是不能修改成员变量的数值。
  • 不能调用非const修饰的成员函数,哪怕这个函数并没有改变成员变量。

        建议只要成员函数不修改成员变量,就是用const修饰,例如get、print、show等显式函数等。

#include <iostream>

using namespace std;

class Demo
{
private:
    int a;
public:
    Demo(int a)
    {
        this->a = a;
    }

    void func0()
    {
        cout << "哈哈哈哈哈" << endl;
    }

    int get_demo()const
    {
        return a;
    }

    void test()const
    {
//        int a = 10;
//        a++;
//        a++;    // 错误const修饰的成员函数,不能修改成员变量
        cout << a << endl;  // 可以调用但是不能修改

//        func0(); // 错误const修饰的成员函数,不能调用非const修饰的成员函数

        get_demo();
    }

};

int main()
{
    Demo d1(1);
    d1.test();

    return 0;
}

8.2 const修饰对象

        const修饰的对象被称为常量对象,这种对象的成员变量值无法修改,也无法调用非const的成员函数。

#include <iostream>

using namespace std;

class Demo
{
private:
    int a;
public:
    int b = 1;
    Demo(int a)
    {
        this->a = a;
    }

    void func0()
    {
        cout << "哈哈哈哈哈" << endl;
    }

    int get_demo()const
    {
        return a;
    }

};

int main()
{
//    const Demo demo(1);
    Demo const demo(1); // 两种初始化方式,等效于上一行

    cout << demo.get_demo() << endl;

//    demo.func0();     // 错误const修饰的对象,无法调用非const修饰的成员函数

    cout << demo.b << endl;
//    demo.b = 10; // 错误 const修饰的对象,无法修改成员变量
    
    return 0;
}
8.3 const修饰成员变量

        const修饰的成员变量,常成员变量,表示该成员变量的值无法被修改。

        常成员变量有两种初始化的方式:

  • 直接赋值

        声明后赋值:

  • 构造初始化列表

        上述两种方式同时使用时,前者失效,以后者为准(构造初始化列表)

#include <iostream>

using namespace std;

class Demo
{
private:
    const int a = 1;    // 直接赋值
    const int b = 2;
    const int c = 3;

public:

    Demo(int a,int b,int c):a(a),b(b),c(c)
    {
//        this->a = a;    // 二次赋值
//        this->b = b;
//        this->c = c;
    }

    void show()
    {
        cout << a << b << c << endl;
    }

    void test()
    {
//        a++;
//        b++;
//        c++;
    }

};

int main()
{
    Demo d1(10,20,30);
    d1.show();
    d1.test();

    return 0;
}
8.4 const修饰局部变量

const修饰局部变量,表示该局部变量无法被修改,这种方法常用于引用参数

#include <iostream>

using namespace std;

class Demo
{
private:
    const int a = 1;    // 直接赋值
    const int b = 2;
    const int c = 3;

public:

    Demo(int a,int b,int c):a(a),b(b),c(c)
    {
//        this->a = a;    // 二次赋值
//        this->b = b;
//        this->c = c;
    }

    void show()
    {
        cout << a << b << c << endl;
    }

    void test()
    {
        const int e = 1;    // const修饰局部变量
//        e = 2; 错误
    }
};

int main()
{
    Demo d1(10,20,30);
    d1.show();
    d1.test();

    return 0;
}

8.5 const修饰引用参数
#include <iostream>

using namespace std;

class Demo
{
private:
    const int a = 1;    // 直接赋值
    const int b = 2;
    const int c = 3;

public:

    Demo(int a,int b,int c):a(a),b(b),c(c)
    {
//        this->a = a;    // 二次赋值
//        this->b = b;
//        this->c = c;
    }

    void show()
    {
        cout << a << b << c << endl;
    }

    void test(const int &f)
    {
        const int e = 1;    // const修饰局部变量
//        e = 2; 错误
    }

};

int main()
{
    Demo d1(10,20,30);
    d1.show();
    int g = 20;
    d1.test(g);

    return 0;
}

四、运算符重载

1、友元

1.1 概念

        友元函数是一种定义在类外的普通函数,但是在调用时需要在类内声明,目的是为了与该类成员函数加以区分,声明时在函数名前添加friend关键字,友元函数不是成员函数,但是他可以访问类中所有成员函数,包括私有成员。

        友元有三种实现方式:

  • 友元函数
  • 友元类
  • 友元成员函数

        友元在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数能够访问类中的私有成员。导致程序的维护性变差,使用友元要慎重。

1.2 友元函数

        友元函数不属于任何一类,是类外的普通函数,可以访问类内所有成员,但是需要在类中进行声明。

        友元函数的使用需要注意以下几点:

  • 友元函数没有this指针
  • 友元函数的”声明“可以放置到类中的任何位置,不受权限修饰符的影响
  • 一个友元函数理论上可以访问多个类,只需要在各个类中分别”声明“。
#include <iostream>

using namespace std;

class Test
{
private:
    int a;
public:
    Test(int i):a(i){}

    void show()
    {
        cout << a << " " << &a << endl;
    }

    // 友元函数,类内声明
    friend void and_test(Test &t);
};

// 友元函数
void and_test(Test &t)
{
//   t.show();
    cout << ++t.a << " " << &t.a << endl;
}

int main()
{
    Test t1(1);
    and_test(t1);
    t1.show();

    return 0;
}

1.3 友元类

        当一个类B成为另一个类Test的朋友是,B可以访问Test中所有的成员,此时认定B是Test的友元类。

        友元类的使用需要注意以下几点:

  • 友元关系不能被继承
  • 友元关系不具有交换性(比如:类B声明成类Test的友元,类B可以访问类Test中的成员,但是类Test不能访问类B中的私有成员,如果需要访问,需要将类Test声明成类B的友元)

        互为友元,需要类内声明,类外实现。

#include <iostream>

using namespace std;

class Test
{
private:
    int a;
public:
    Test(int i):a(i){}

    void show()
    {
        cout << a << " " << &a << endl;
    }

    // 友元类,类内声明
    friend class B;
};


class B
{
public:
    void and_test(Test &t)
    {
        cout << ++t.a << " " << &t.a << endl;
    }
};

int main()
{
    Test t1(1);
    B b;
    b.and_test(t1);
    t1.show();

    return 0;
}

1.4 友元成员函数

#include <iostream>

using namespace std;

// 第四步:声明被访问的类
class Test;

class B
{
public:
    // 第二步:声明友元成员函数(类内声明,类外实现)
    void and_test(Test &t);
};

class Test
{
private:
    int a;
public:
    Test(int i):a(i){}

    void show()
    {
        cout << a << " " << &a << endl;
    }

    // 友元成员函数,类内声明:第一步确定友元成员函数的格式并声明
    friend  void B::and_test(Test &t);
};



// 第三步:类外定义友元成员函数
void B::and_test(Test &t)
{
    cout << ++t.a << " " << &t.a << endl;
}



int main()
{
    Test t1(1);
    B b;
    b.and_test(t1);
    t1.show();

    return 0;
}

2、运算符重载

2.1  概念

        C++中可以把部分运算符看做成函数,此时运算符也可以重载。

        运算符预定义的操作只能针对基本数据类型,但是对于自定义类型,也需要类似的运算操作,此时就可以重新定义这些运算符的功能,使其支持特定类型,完成特定的操作。

        运算符重载有两种实现方式:

  • 友元函数运算符重载
  • 成员函数运算符重载

2.2 友元函数运算符重载

#include <iostream>

using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // + 运算符重载
    friend MyInt operator+(MyInt &i,MyInt &i2);
};


MyInt operator+ (MyInt &i,MyInt &i2)
{
//    MyInt int4(0);
//    int4.a = i.a + i2.a;
//    return int4;
    return i.a + i2.a;
}


int main()
{
    MyInt int1(2);
    cout << int1.get_int() << endl; // 2
    MyInt int2(int1);   // 拷贝构造函数
    cout << int2.get_int() << endl; // 2

    MyInt int3 = int1 + int2;
    cout << int3.get_int() << endl;

    return 0;
}
2.2.1 ++运算符重载
#include <iostream>

using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // + 运算符重载
    friend MyInt operator+(MyInt &i,MyInt &i2);

    // ++ 运算符重载
    friend MyInt operator ++(MyInt &i); // 前置自增
    friend MyInt operator ++(MyInt &i,int); // 后置自增
};


MyInt operator+ (MyInt &i,MyInt &i2)
{
//    MyInt int4(0);
//    int4.a = i.a + i2.a;
//    return int4;

    return i.a + i2.a;
}

MyInt operator ++(MyInt &i)
{
    ++i.a;
    return i;
}


MyInt operator ++(MyInt &i,int)
{
    return i.a++;
}


int main()
{
    MyInt int1(2);
    cout << int1.get_int() << endl; // 2
    MyInt int2(int1);   // 拷贝构造函数
    cout << int2.get_int() << endl; // 2

    MyInt int3 = int1 + int2;
    cout << int3.get_int() << endl;

    ++int1;
    cout << int1.get_int() << endl; // 3

    cout << (int1++).get_int() << endl; // 3
    cout << int1.get_int() << endl; // 4

    return 0;
}

2.3 成员函数运算符重载

        成员函数运算符重载相比于友元函数重载,最主要的区别在于,友元函数的第一个输出参数,在成员函数中使用this指针代替,因此同样的运算符重载,成员函数运算符重载比友元函数运算符重载参数少一个。

#include <iostream>

using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }

    // + 运算符重载
    MyInt operator+(MyInt &i2);

    // ++ 运算符重载
    MyInt operator ++(); // 前置自增
    MyInt operator ++(int); // 后置自增
};


MyInt MyInt::operator+ (MyInt &i2)
{
    return this->a + i2.a;
}

MyInt MyInt::operator ++()
{
    return ++this->a;
}


MyInt MyInt::operator ++(int)
{
    return this->a++;
}


int main()
{
    MyInt int1(2);
    cout << int1.get_int() << endl; // 2
    MyInt int2(int1);   // 拷贝构造函数
    cout << int2.get_int() << endl; // 2

    MyInt int3 = int1 + int2;
    cout << int3.get_int() << endl;

    ++int1;
    cout << int1.get_int() << endl; // 3

    cout << (int1++).get_int() << endl; // 3
    cout << int1.get_int() << endl; // 4

    return 0;
}

2.4 特殊运算符重载

2.4.1 赋值运算符重

        除了之前学习的无参构造函数、拷贝构造函数与析构函数以外,如果程序员不手写,编译器还会给一个类添加赋值运算符重载函数。

        赋值运算符重载只能使用成员函数运算符重载。

        当类中出现指针类型的成员变量时,默认的赋值运算符重载函数类似于默认的浅拷贝构造函数出现的问题。因此也需要手写编写解决”浅拷贝“的问题。

#include <iostream>

using namespace std;

class MyInt
{
private:
    int a;
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }
    // 编译器自动添加的赋值运算符重载函数
    MyInt & operator =(MyInt &i)
    {
        cout << "赋值运算符重载函数被调用了" << endl;
        this->a = i.a;
        return *this;
    }

};


int main()
{
    MyInt int1(2);
    MyInt int2(3);

    MyInt int3 = int1;   // 拷贝构造函数隐式调用
    int3 = int2;    // 赋值运算符重载
    cout << int3.get_int() << endl;
       

    return 0;
}
2.4.2 类型转换运算符重载        

        必须使用成员函数运算符重载,且格式比较特殊

#include <iostream>

using namespace std;

class MyInt
{
private:
    int a;
    string str = "hello";
public:
    MyInt(int a):a(a){}

    int get_int()
    {
        return a;
    }
    // 编译器自动添加的赋值运算符重载函数
    MyInt & operator =(MyInt &i)
    {
        cout << "赋值运算符重载函数被调用了" << endl;
        this->a = i.a;
        return *this;
    }

    // 类型转换运算符重载
    operator int()
    {
        return a;
    }

    operator string()
    {
        return str;
    }


};

int main()
{
    MyInt int1(2);

    int a = int1;
    cout << a << endl;

    string str  = int1;
    cout << str << endl;
    return 0;
}

2.5 注意事项

  • 重载运算符限制在C++语言中的已有的运算符范围,不能创建新的运算符。
  • 运算符重载本质上也是函数重载, 但是不支持函数参数默认值设定。
  • 重载之后的运算符不能改变运算符的优先级和结合性。也不能改变运算符的操作数和语法结构。
  • 运算符重载必须基于或包含自定义类型,即不能改变基本数据类型的运算规则。
  • 重载的功能应该与原有功能相似,避免没有目的的滥用运算符重载。
  • 一般情况下,双目运算符建议使用友元函数运算符重载,单目运算符建议使用成员函数运算符重载。

五、模板与容器

1、模板

        模板可以让类或者函数支持一种通用类型,这种通用的类在实际运用中可以是用任何数据类类型,因此程序员可以写出一些与类无关的代码,这种编程方式也被成为“泛型编程”

        通常有两种表示形式:

  • 函数模板
  • 类模板

1.1 函数模板

        使一个函数支持模板编程,可以让函数支持通用数据类型

#include <iostream>
#include <string.h>

using namespace std;

template<class T>   // 可以是class也可以是typename
T add(T a, T b)
{
    return a + b;
}


int main()
{
    string str = "10";
    string str2 = "20";
    cout << add(2,3) << endl;

    return 0;
}

1.2 类模板

        使一个类支持模板编程的模型,可以使一个类支持通用数据类型

#include <iostream>
#include <string.h>

using namespace std;

template<class T>   // 可以是class也可以是typename
T add(T a, T b)
{
    return a + b;
}

template<class T>   // 可以是class也可以是typename
class Test
{
private:
    T val;
public:
    Test(T v):val(v){}

    T get_val()const
    {
        return val;
    }

    void set_val(const T &val)
    {
        this->val = val;
    }
};


int main()
{
//    Test t1(20);
//    cout << t1.get_val() << endl;
    Test<int> t1(20);
    cout << t1.get_val() << endl;

    t1.set_val(2.354);
    cout << t1.get_val() << endl;   // 数据窄化输出2

    Test<double> t2(0.2);
    cout << t2.get_val() << endl;   // 0.2

    Test<string> t3("你好");
    cout << t3.get_val() << endl;

    return 0;
}

2、容器

2.1 标准模板库STL

        标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。虽说它主要出现到了C++中,但是在被引入C++之前该技术就已经存在了很长时间。

        STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

2.2 概念

        容器是用来存储数据的集合,数据元素可以是任意类型(基于模板实现),使用时要引入对应头文件

2.3 顺序容器

        该容器中每个元素都有固定位置且呈线性排布,除非使用者删除或者插入来改变元素位置

2.3.1 array数组

        array数组是C++11新增的容器类型,与传统数组相比更加安全,易于使用。array数组是定长的。没有办法方便的进行伸缩。

#include <iostream>
#include <array>

using namespace std;


int main()
{
    // 创建一个长度为5的int数组
    array<int,5> arr = {1,54,3}; // 后面两位补零
    cout << arr[0] << endl; // 1
    cout << arr[4] << endl; // 0

    // 推荐使用
    cout << arr.at(2) << endl;  // 3

    arr[3] = 200;

    // for循环
    for(int i = 0; i < arr.size(); ++i)
    {
        cout << arr.at(i) << endl;
    }

    // for each
    for(int i : arr)
    {
        cout << i << endl;
    }
    
    // 迭代器遍历,后边讲

    return 0;
}

2.3.2 vector向量

        内部由数组实现,擅长存取,不擅长删插

#include <iostream>
#include <vector>

using namespace std;


int main()
{
    //    vector<int> v = {1,2,3};
    //    cout << v.size() << endl;
    //    for(int i : v)
    //    {
    //        cout << i << endl;
    //    }

    // 创建一个长度为5的int向量
    vector<int> vec(5);
    //    for(int i : vec)
    //    {
    //        cout << i << endl;
    //    }

    // 增
    vec.push_back(222);
//    cout << vec.size() << endl; // 6

    vec.insert(vec.begin()+2,333);  // begin()可以返回指向第一个元素的迭代器指针,+2是在第三个位置上插入333

    // 改
    vec.at(0) = 111;
    vec[1] = 222;
    vec.at(3) = 444;
    vec.at(4) = 555;
    vec.at(5) = 666;

    // 删除
    // 删除最后一个元素
    vec.pop_back();

    vec.erase(vec.begin()+1);   // 删除第二个元素


    vec.erase(vec.end()-2); // 删除倒数第二个元素

    // 查
    cout << vec[0] << endl;
    cout << vec.at(1) << endl;
    for(int i : vec)
    {
        cout << i << " ";
    }
    cout << endl;
    // 遍历
    for(int i = 0; i < vec.size(); ++i)
    {
        cout << vec.at(i) << endl;
    }

    // 迭代器遍历,先省略

    // 判断是否为空,1空,0就是非空
    cout << vec.empty() << endl; // 0

    // 清空
    vec.clear();
    cout << vec.empty() << endl;    // 1
    cout << vec.size() << endl; // 0
    return 0;
}
2.3.3 list列表

        list由双向链表实现,内存空间不连续,不支持下标访问。擅长删、插,不擅长存取

#include <iostream>
#include <list>

using namespace std;


int main()
{
    //    // 创建了一个默认无数值的list
    //    list<string> lis1;

    //    // 创建一个长度为2的列表,第一个元素hello,第二个元素world
    //    list<string> lis2{"hello","world"};
    //    for(string s : lis2)
    //    {
    //        cout << s << endl;
    //    }

    // 创建一个长度为5的列表,每个元素都是hello
    list<string>lis(5,"hello");

    // 增
    lis.push_back("world"); // 向后追加单元素
    lis.push_front("haha"); // 向前插入单元素

    lis.insert(++lis.begin(),"222");    // 在第二个位置上插入“222”


    // 删除
    // lis.pop_back(); // 删除最后一个元素
    lis.pop_front();    // 删除第一个元素


    // 迭代器操作
    // 保存迭代器指针
    list<string>::iterator iter = lis.begin();
    advance(iter,1);    // 移动迭代器
    lis.insert(iter,"333"); // 插入333

    // 删除最后一个元素
//    iter = lis.end();
//    iter--;
//    lis.erase(iter);

    iter = lis.begin();
    advance(iter,1);
    lis.erase(iter);

    // 返回第一个元素的引用
    cout << lis.front() << endl;

    // 返回最后一个元素的引用
    cout << lis.back() << endl;

    // 改
    iter = lis.end();
    advance(iter,2);
    *iter = "200";

    // 查
    cout << "-----" << *iter << endl;


    // 不支持普通循环,因为不支持下标

    // for each 可以使用
    for(string s : lis)
    {
        cout << s << endl;
    }

    // 也支持迭代器遍历

    // 清空
    lis.clear();
    cout << lis.size() << endl; // 0

    return 0;
}
2.3.4 deque队列

        deque几乎支持所有vector的API,性能位于vector与list两者之间。最擅长两端存取的顺序容器。

#include <iostream>
#include <deque>

using namespace std;


int main()
{
    //    deque<int> v = {1,2,3};
    //    cout << v.size() << endl;
    //    for(int i : v)
    //    {
    //        cout << i << endl;
    //    }

    // 创建一个长度为5的int向量
    deque<int> deq(5);
    //    for(int i : deq)
    //    {
    //        cout << i << endl;
    //    }

    // 增
    deq.push_back(222);
//    cout << deq.size() << endl; // 6

    deq.insert(deq.begin()+2,333);  // begin()可以返回指向第一个元素的迭代器指针,+2是在第三个位置上插入333

    // 改
    deq.at(0) = 111;
    deq[1] = 222;
    deq.at(3) = 444;
    deq.at(4) = 555;
    deq.at(5) = 666;

    // 删除
    // 删除最后一个元素
    deq.pop_back();

    deq.erase(deq.begin()+1);   // 删除第二个元素


    deq.erase(deq.end()-2); // 删除倒数第二个元素

    // 查
    cout << deq[0] << endl;
    cout << deq.at(1) << endl;
    for(int i : deq)
    {
        cout << i << " ";
    }
    cout << endl;
    // 遍历
    for(int i = 0; i < deq.size(); ++i)
    {
        cout << deq.at(i) << endl;
    }

    // 迭代器遍历,先省略

    // 判断是否为空,1空,0就是非空
    cout << deq.empty() << endl; // 0

    // 清空
    deq.clear();
    cout << deq.empty() << endl;    // 1
    cout << deq.size() << endl; // 0
    return 0;
}

2.4 关联容器

        管两天容器中各个元素之间没有严格顺序,但是其中的键值具有唯一性,通过键可以找到对应的值

#include <iostream>
#include <map>

using namespace std;


int main()
{
    // 创建了一个map容器的对象ma1
    // 列表初始化,C++11支持
    map<string,int> ma1 = {{"年龄",18},{"体重",200}}; // 有两个元素
    cout << ma1.size() << endl; // 2

    // 创建一个元素为0的键值对对象
    map<string,int> ma;
    cout << ma.size() << endl;  // 0

    // 增
    ma["身高"] = 226; // 插入元素
    ma.insert(pair<string,int>("体重",300));  // 插入元素

    // 查
    cout << ma["身高"] << endl;   // 226
    cout << ma["体重"] << endl;   // 300
    if(ma.find("身高") == ma.end())   // find从头开始查找,查找不到返回end
    {
        cout << "没有找到身高元素" << endl;
    }
    else
    {
        cout << ma["身高"] << endl;
    }


    // 改
    ma["身高"] = 50;  // 修改元素
    cout << ma["身高"] << endl;


    // 删,删除之前可以判断元素是否存在
//    int re = ma.erase("身高");    // 删除成功返回1,失败返回0
//    cout << "身高删除的返回值:" <<re << endl;

//    re = ma.erase("身高");
//    cout << "身高删除的返回值:" <<re << endl;   // 0

//    cout << ma.size() << endl;  // 1

//    ma.clear();
//    cout << ma.size() << endl;

    // for each
    for(pair<string,int>pa : ma)
    {
        cout << pa.first << " " << pa.second << endl;
    }

    return 0;
}

2.5 迭代器遍历

        迭代器使特殊的指针,用于容器中元素的读写以及遍历

        如果迭代器不对数值进行修改,建议只使用只读迭代器 const_iterator, 返回值用 iterator

#include <iostream>
#include <map>
#include <array>
#include <vector>
#include <list>
#include <deque>

using namespace std;


int main()
{
    string s = "abcdefg";
    // 迭代器遍历string
    for(string::const_iterator iter = s.begin(); iter != s.end();iter++)
    {
        cout << *iter << endl;
    }

    cout << "------------------" << endl;

    array<int,5> arr = {23,45,66,22,34};
    for(array<int,5>::const_iterator iter = arr.begin();iter != arr.end();++iter)
    {
        cout << *iter << endl;
    }

    cout << "------------------" << endl;
    vector<string>vec(6,"hello");
    for(vector<string>::const_iterator iter = vec.begin();iter != vec.end();iter++)
    {
        cout << *iter << endl;
    }

    cout << "------------------" << endl;
    list<string> lis(6,"world");
    for(list<string>::const_iterator iter = lis.begin();iter != lis.end();iter++)
    {
         cout << *iter << endl;
    }

    cout << "------------------" << endl;
    deque<string>de(6,"hahaha");
    for(deque<string>::const_iterator iter = de.begin();iter != de.end();iter++)
    {
         cout << *iter << endl;
    }

    cout << "------------------" << endl;
    map<string,int> ma;
    ma["年龄"] = 20;
    ma["身高"] = 185;
    ma["体重"] = 75;

    for(map<string,int>::const_iterator iter = ma.begin(); iter != ma.end(); iter++)
    {
        // first 是键,second是值
        cout << iter->first << " " << iter->second << endl;
    }

    return 0;
}

六、面向对象核心

1、继承

1.1概念

        继承使面向对象的三大特性之一。体现了代码复用的思想

        继承就是在一个类的基础上新建一个类,并拥有之前一个类的特性

  • 已存在的类被称为“基类”或者“父类”
  • 新建立的被称为“派生类”或者“子类”
#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name = "孙";
public:
    void set_name(string name)
    {
        this->name = name;
    }

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout << "开挖掘机" << endl;
    }
};

// 派生类
class Son:public Father
{

};


int main()
{
    Son son;
    cout << son.get_name() << endl;
    son.work();

    return 0;
}

        面的代码,Son类的功能几乎与Father类重叠。在实际的使用过程中,派生类会做出一些与基类的差异化。

  • 修改继承来的基类内容

        属性:1、公有属性可以直接更改。更改后基类中的属性也会被改变。因为改的是同一份变量。私有属性,需要使用基类提供的公有函数进行更改。

        行为:函数隐藏,通过派生类实现一个同名同参数的函数,来隐藏基类的函数。

  • #include <iostream>
    
    using namespace std;
    
    // 基类
    class Father
    {
    private:
        string name = "孙";
    public:
        void set_name(string name)
        {
            this->name = name;
        }
    
        string get_name()
        {
            return name;
        }
    
        void work()
        {
            cout << "开挖掘机" << endl;
        }
    };
    
    // 派生类
    class Son:public Father
    {
    public:
        void inti()
        {
           set_name("王");
        }
    
        void work()
        {
            cout << "我不光干活,我还唱跳rap打篮球" << endl;
        }
    
        void game()
        {
            cout << "原神启动!!!!!" << endl;
        }
    
    };
    
    
    int main()
    {
        Son son;
        son.inti();
        cout << son.get_name() << endl; //  王
        son.work(); // 我不光干活,我还唱跳rap打篮球
        son.game(); // 原神启动!!!!!
    
        son.Father::work(); // 开挖掘机,调用基类被隐藏的成员函数
    
        return 0;
    }
    
    新增派生类的内容
#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name = "孙";
public:
    void set_name(string name)
    {
        this->name = name;
    }

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout << "开挖掘机" << endl;
    }
};

// 派生类
class Son:public Father
{
public:
    void inti()
    {
       set_name("王");
    }

    void work()
    {
        cout << "我不光干活,我还唱跳rap打篮球" << endl;
    }

    void game()
    {
        cout << "原神启动!!!!!" << endl;
    }

};


int main()
{
    Son son;
    son.inti();
    cout << son.get_name() << endl; //  王
    son.work(); // 我不光干活,我还唱跳rap打篮球
    son.game(); // 原神启动!!!!!

    son.Father::work(); // 开挖掘机,调用基类被隐藏的成员函数

    return 0;
}

1.2 构造函数

1.2.1 派生类与基类构造函数的和关系

        构造函数与析构函数不能被继承

include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name;
public:

    Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
};

// 派生类
class Son:public Father
{
public:

};


int main()
{
//    Son son;  // 找不到基类的无参构造函数
//    Son son("张"); // 没有派生类的有参构造函数



    return 0;
}

1.2.2 解决方案
1.2.2.1 补充基类的无参构造函数
#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name;
public:
    Father()
    {
        name = "张";
    }
    Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
};

// 派生类
class Son:public Father
{
public:

};


int main()
{
    Son son;
    cout << son.get_name() << endl;
//    Son son("张"); // 没有派生类的有参构造函数


    return 0;
}
1.2.2.2 手动在派生类中调用基类构造函数
1.2.2.2.1 透传构造        
#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name;
public:
    Father()
    {
        name = "张";
    }
    Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
};

// 派生类
class Son:public Father
{
public:

    // 编译器会自动添加构造函数。透传构造
//    Son():Father(){}
    // 手动添加构造函数。透传构造
    Son():Father("王"){}

    Son(string fn):Father(fn){}


};


int main()
{
    Son son;
    cout << son.get_name() << endl; // 王
    Son son1("张");
    cout << son1.get_name() << endl; // 张


    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值