C++课程学习记录

这篇具有很好参考价值的文章主要介绍了C++课程学习记录。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前置说明

这篇博客是用来记录期中考试过后我对C++的课上复现以及课后练习的过程

  • 知识点请食用目录进行参考
  • 未涉及的知识点请食用我在之前写过の一篇期中机考总结

传送门:C++期中机考试题


2. 二叉树的模拟

2.1 参考资料

  • 先贴上这一篇对我影响颇深的博客,没有他我怎么都想不明白二叉树的三种遍历方式
    数据结构——二叉树先序、中序、后序及层次四种遍历(C语言版)
  • 由于二叉树的构建其中一种涉及到了递归建树,我再贴一篇博客,以此记录这篇博客对我递归理解的启发与深远影响
    100天精通Python(基础篇)——第10天:函数进阶
  • 最后再贴一篇,除了课上听的与结合chat问出来的,这篇博客写的也很详细,尤其是代码部分比较符合我的习惯,也算帮了大忙。并且,由于以下我只涉及到了二叉树的构建与遍历,其他的包括删除结点与查询结点并未涉及,大家可以按照需要进行补充
    二叉排序树(二叉查找树、二叉搜索树)(图解+完整代码)

2.2 二叉树的构建

2.2.1 递归构建
#include <iostream>

using namespace std;

struct Node
{
    int val;
    Node* left;
    Node* right;

    // 创建新结点时,同时重置左右结点为空结点
    Node(int _val) : val(_val), left(nullptr), right(nullptr) {}
};

class BinaryTree
{
private:
    Node* root;

    // 找到适合插入的位置并创建结点,返回所创建结点的地址
    Node* Insert(Node* now, int x)
    {
        if (now == nullptr)
            now = new Node(x);
        else if (x < now->val)
            now->left = Insert(now->left, x);
        else
            now->right = Insert(now->right, x);

        return now;
    }

    void Output(Node* now)
    {
        if (now == nullptr) return;

        Output(now->left);
        cout << now->val << ' ';
        Output(now->right);
    }

public:
    BinaryTree() : root(nullptr) {}

    // 递归构建二叉树
    void Insert(int x)
    {
        root = Insert(root, x);
    }

    // 保护私有数据root
    void Output()
    {
        Output(root);
    }
};

int main()
{
    BinaryTree Tree;

    int a[] = {3, 2, 4, 1, 5, 7, 9, 6, 8}, n = 9;

    for (int i = 0; i < n; i++)
        Tree.Insert(a[i]);

    Tree.Output();

    return 0;
}
2.2.2 迭代构建
#include <iostream>

using namespace std;

struct Node
{
    int val;
    Node* left;
    Node* right;

    // 创建新结点时,同时重置左右结点为空结点
    Node(int _val) : val(_val), left(nullptr), right(nullptr) {}
};

class BinaryTree
{
private:
    Node* root;

    void Output(Node* node)
    {
        if (node == nullptr) return;

        Output(node->left);
        cout << node->val << ' ';
        Output(node->right);
    }

public:
    BinaryTree() : root(nullptr) {}

    // 迭代构建二叉树
    void Insert(int x)
    {
        if (root == nullptr)
        {
            root = new Node(x);
            return;
        }
        Node* cur = root;
        while (true)
        {
            if (x == cur->val) break;
            else if (x < cur->val)
            {
                if (cur->left == nullptr)
                {
                    cur->left = new Node(x);
                    break;
                }
                cur = cur->left;
            }
            else
            {
                if (cur->right == nullptr)
                {
                    cur->right = new Node(x);
                    break;
                }
                cur = cur->right;
            }
        }
    }

    // 保护私有数据root
    void Output()
    {
        Output(root);
    }
};

int main()
{
    BinaryTree Tree;

    int a[] = {3, 2, 4, 1, 5, 7, 9, 6, 8}, n = 9;

    for (int i = 0; i < n; i++)
        Tree.Insert(a[i]);

    Tree.Output();

    return 0;
}

三种输出结果:

前序遍历:3, 2, 1, 4, 5, 7, 6, 9, 8
中序遍历:1, 2, 3, 4, 5, 6, 7, 8, 9
后序遍历:1, 2, 6, 8, 9, 7, 5, 4, 3

2.3 二叉树的遍历

伪代码:(中序遍历为例)
---------------------
传入结点
if (结点为空) return;

传入结点的左指针;
输出;
传入结点的右指针;

2.4 二叉树的应用

赠送一道算法题
AcWing 3384. 二叉树遍历

AC代码

#include <iostream>

using namespace std;

string s;
int pos;

struct Node
{
    char val;
    Node* left;
    Node* right;

    Node(char x) : val(x), left(nullptr), right(nullptr) {}
};

class BinaryTree
{
private:
    Node* root;

    Node* build()
    {
        char c = s[pos++];
        if (c == '#') return nullptr;

        Node* node = new Node(c);
        node->left = build();
        node->right = build();

        return node;
    }

    void OutputIn(Node* now)
    {
        if (now == nullptr) return;

        OutputIn(now->left);
        cout << now->val << ' ';
        OutputIn(now->right);
    }

public:
    BinaryTree() : root(nullptr) {}

    void buildTree()
    {
        root = build();
    }

    void OutputIn()
    {
        OutputIn(root);
    }
};

int main()
{
    BinaryTree Tree;

    cin >> s;

    Tree.buildTree();

    Tree.OutputIn();

    return 0;
}

3. 继承与派生

参考书籍:《C++面向对象程序设计》 ——科学出版社

3.1 最简单的生死

#include <iostream>
using namespace std;

class A
{
private:
	int x;
public:
	A(int x = 0) : x(x)
	{
		cout << x << endl;
	}
	virtual ~A()
	{
		cout << -x << endl;
	}
};

class AA :public A
{
private:
	int xx;
public:
	AA(int x, int xx) : A(x), xx(xx)
	{
		cout << xx << endl;
	}
	~AA()
	{
		cout << -xx << endl;
	}
};

int main()
{
	AA a1(3, 4);
	AA a2(2, 5);
	return 0;
}
输出:
3
4
2
5
-5
-2
-4
-3
  • 列表化初始化的时候感觉和父类这个对象是当前子类的成员变量一样
  • 析构顺序为:父生,子生,子死,父死

3.2 动态申请空间的生死

类与上述一致

int main()
{
	A* a = new A(2);
	AA* aa = new AA(3, 4);
	delete a;
	delete aa;
	return 0;
}
  • 这没啥好说的想谁生就生,想谁析构就让谁析构
  • 父类的使用与平常一致,没有影响
  • 有一个好玩的地方就是关于析构函数中virtual关键字是否使用的问题。如果当前类可继承,并且确实被继承了,那么它的析构函数就一定要加virtual关键字,这样才能确保内存释放干净
  • 可以将基类与派生类之间的继承关系理解为一棵多叉树,其中最开始被继承的基类为根节点。假设此时有一个例子为:利用一个A类的指针创建一个B类的对象A* ptr = new B,那么
    • 创建过程:
      1. 从B结点开始递归搜索到A结点
      2. 从A结点开始依次执行到B结点
    • 销毁过程:
      1. ptr所属对象A的结点开始递归搜索到B结点
      2. 从B结点开始依次向A结点销毁

3.3 继承中的protectd权限

#include<iostream> 
using namespace std;

class A
{
private:
	int x1;  // 完全是自己控制
public:
	int x2;  // 完全供大家控制
protected:   // 传给子类
	int x3;
public:
	A(int x1, int x2, int x3) :x1(x1), x2(x2), x3(x3)
	{
		cout << x1 << " " << x2 << " " << x3 << endl;
	}
	virtual ~A()
	{
		cout << -x1 << " " << -x2 << " " << -x3 << endl;
	}
};
class AA : public A
{
	int xx;
public:
	AA(int x1, int x2, int x3, int xx) :A(x1, x2, x3), xx(xx)
	{
		cout << x1 << " " << x2 << " " << x3 << " " << xx << endl;
	}
	~AA()
	{
		// cout<<-x1  不能存取父类中的私有成员
		cout << -x2 << " " << -x3 << " " << -xx << endl;
	}
};

int main()
{
	A* p1 = new  A(1, 2, 3);
	delete p1;

	AA* p2 = new AA(4, 5, 6, 7); // 此程序等价于A* p2 = new AA(4, 5, 6, 7);
	delete p2;
	
	return 0;
}
输出:
1 2 3
-1 -2 -3
4 5 6
4 5 6 7
-5 -6 -7
-4 -5 -6
  • protected的数据成员只可以在这个类的派生类中使用,其他地方都不可以调用
  • 基类的private数据成员只可以在这个类中使用,其他地方包括它的派生类都不可以访问

3.4 三种继承方式

  • 公有继承

    就是没啥区别,这是主要工程上主要使用的继承方式

  • 私有继承

    可以理解为

    • 将基类的公有与保护的成员全部变成私有类型了,类外都不能访问到基类的public成员了
    • 将基类的公有与保护的成员作为自己的私有成员继承下来了,后续的派生类都没有访问的权限
  • 保护继承
    • 可以理解为将基类的公有的成员全部变成保护类型了,类外同样都不能访问到基类的public成员了
    • 将基类的公有成员作为自己的保护成员继承下来了,后续的派生类任然可以访问将基类的public成员作为protected成员进行访问

3.5 修改某些继承成员的继承类型

只是在派生类中对继承下来的成员进行了继承方式的修改,并不影响基类中成员本身的权限属性

举个例子

#include <iostream>
#include <cstring>
using namespace std;

// 在派生类中将基类的某些特点成员重继承为与派生类的继承方式不同的成员

class A
{
private:
protected:
public:
	void f1()
	{
		cout << "默认私有继承后,外界无法访问到基类中的公有成员了" << "\n";
	}
	void f2()
	{
		cout << "我让这个普通函数变为外界可访问" << "\n";
	}
	void f3()
	{
		cout << "我让这个函数变为外界可访问" << "\n";
	}
	void f3(int)
	{
		cout << "我让这个重载函数通过一句话也变为外界可访问" << "\n";
	}
};

class B :A // 默认私有继承
{
private:
protected:
public:
	A::f2;
	A::f3; // 这一句话就可以改变所有的同名的重载函数
};

int main()
{
	B object;

//	object.f1(); “A::f1”不可访问,因为“B”使用“private”从“A”继承
	object.f2();
	object.f3();
	object.f3(1);

	return 0;
}

再举个例子

#include <iostream>
#include <cstring>
using namespace std;

// 在派生类中将基类的某些特点成员重继承为与派生类的继承方式不同的成员

class A
{
private:
protected:
public:
	void f1()
	{
		cout << "默认私有继承后,外界无法访问到基类中的公有成员了" << "\n";
	}
	void f2()
	{
		cout << "我让这个普通函数变为外界可访问" << "\n";
	}
	void f3()
	{
		cout << "我让这个函数变为外界可访问" << "\n";
	}
	void f3(int)
	{
		cout << "我让这个重载函数通过一句话也变为外界可访问" << "\n";
	}
};

class B :A // 默认私有继承
{
private:
protected:
public:
	A::f2;
	// 这样写就可以改变特定的重载函数的开放权限了
	void f3()
	{
		A::f3();
	}
};

int main()
{
	B object;

//	object.f1(); “A::f1”不可访问,因为“B”使用“private”从“A”继承
	object.f2();
	object.f3();
//	object.f3(1);“B::f3”: 函数不接受 1 个参数

	return 0;
}

3.6 多级派生

没什么好多说的,上代码!代码看懂了,输出结果看懂了,就OK了
唯一需要强调的一点是,在多级派生时,对于一个派生类对象的初始化,一定要附带着把他所有的“爹”也全部初始化,否则调用默认构造函数初始化他的所有“爹”

#include <iostream>
#include <string>
using namespace std;

class A
{
protected:
	int id;
	string name;
public:
	A(int _id = 0, string _name = "hehe") : id(_id), name(_name) 
	{
		cout << "基类A被创建" << endl;
	}
	~A() 
	{ 
		cout << "基类A被销毁" << endl; 
	}
	int get_id() { return id; }
	string get_name() { return name; }
	void show_A()
	{
		cout << "id: " << "\t" << get_id() << endl;
		cout << "name: " << "\t" << get_name() << endl;
	}
};

class B : public A
{
protected:
	double score;
public:
	B(int _id = 0, string _name = "hehe", double _score = 99.5)
		: A(_id, _name), score(_score)
	{
		cout << "直接派生类B被创建" << endl;
	}
	virtual ~B()
	{
		cout << "直接派生类B被销毁" << endl;
	}
	double get_score() { return score; }
	void show_B()
	{
		show_A();
		cout << "score: " << "\t" << get_score() << endl;
	}
};

class C : public B
{
protected:
	int age;
public:
	C(int _id = 0, string _name = "hehe", double _score = 99.5, int _age = 18)
		: B(_id, _name, _score), age(_age)
	{
		cout << "间接派生类C被创建" << endl;
	}
	virtual ~C()
	{
		cout << "间接派生类C被销毁" << endl;
	}
	int get_age() { return age; }
	void show_C()
	{
		show_B(); 
		cout << "age: " << "\t" << get_age() << endl;
	}
};

void Test()
{
	C obj(1, "dwj", 100.0, 16);
	obj.show_C();	
}

int main()
{
	Test();
	return 0;
}
// 输出结果
基类A被创建
直接派生类B被创建
间接派生类C被创建
id:     1
name:   dwj
score:  100
age:    16
间接派生类C被销毁
直接派生类B被销毁
基类A被销毁

由于是公有继承,故在C类创建的对象中,可以调用父类的全部public以及protected方法,当然自己的什么都可以调用啦~
C++课程学习记录

3.7 多重继承

也就是一个派生类继承了 ≥ 2 \ge2 2个基类

3.7.1 变量域覆盖

当前对象的成员如果与基类的成员重名或者函数重名并且参数列表完全一致,就会被当前的派生类完全覆盖,以下为变量名覆盖

程序

#include <iostream>
#include <string>
using namespace std;

class A
{
protected:
    string name;
public:
    A(string _name) : name(_name) {}
};

class B
{
protected:
    string name;
public:
    B(string _name) : name(_name) {}
};

class C : public A, public B
{
private:
    string name;
public:
    C(string _name_A, string _name_B, string _name_C)
    : A(_name_A), B(_name_B), name(_name_C) {}
    void show()
    {
        cout  << "A的name: " << name << endl;
        cout  << "B的name: " << name << endl;
        cout  << "C的name: " << name << endl;
    }
};

int main()
{
    C test("Tom", "Jerry", "John");

    test.show();

    return 0;
}

输出结果

A的name: John
B的name: John
C的name: John

Process finished with exit code 0
3.7.2 使用域作用运算符分辨成员

修改后的类C

class C : public A, public B
{
public:
    C(string _name_A, string _name_B)
    : A(_name_A), B(_name_B) {}
    void show()
    {
        cout  << "A的name: " << name << endl;
        cout  << "B的name: " << name << endl;
    }
};

运行后错误提示为

error: reference to 'name' is ambiguous

修改类C

class C : public A, public B
{
public:
    C(string _name_A, string _name_B)
    : A(_name_A), B(_name_B) {}
    void show()
    {
        cout  << "A的name: " << A::name << endl;
        cout  << "B的name: " << B::name << endl;
    }
};

运行结果

A的name: Tom
B的name: Jerry

Process finished with exit code 0

3.8 虚基类

无论虚基类出现在继承层次的哪个位置上,他们都是在非虚基类之前被构造。有虚基类的派生类的构造函数的调用次序是:编译器按照直接基类的声明次序,检查虚基类的出现情况,每个继承子树按照深度优先的顺序检查并调用虚基类的构造函数。虚基类多次出现只调用一次构造函数。虚基类的构造函数调用完之后,再按照声明的顺序调用非虚基类的构造函数(真的是又臭又长😢)

总结一下就是两点:

  1. 按照声明顺序,深度优先搜索虚基类,后序构造
  2. 按照声明顺序,深度优先搜索非虚基类,后序构造

举三个例子就知道了(图中虚继承用虚线表示)


例一:

class A { };
class B : public virtual A { }; 
class C : public virtual A { };
class D : public B, public C { };

C++课程学习记录

那么D对象创建时,构造函数的调用顺序是:A, B, C, D


例二:

class A { };
class B { };
class C : public B, public virtual A { };
class D : public virtual A { };
class E : public C, public virtual D { };

C++课程学习记录

那么E对象创建时,构造函数的调用顺序是:A, D, B, C, E


例三:

class A {};
class B : public A {};
class C {};
class D {};
class E : public virtual D {};
class F : public B, public E, public virtual C {};

C++课程学习记录

那么F对象创建时,构造函数的调用顺序是:D, C, A, B, E, F


3.9 基类和派生类的转换

大概就是对于一个派生类,在赋值 or 指针传递 or 引用传递给一个基类对象 or 基类指针 or 基类引用时,会失去派生类的另外加的成员变量,变成一个基类的对象。为了通过基类的指针,改变派生类的成员方法,引出了C++中的一大特性:多态性

4. 多态性与虚函数

4.1 前言

先补充一下函数调用绑定的知识点:分为静态绑定与动态绑定

静态绑定:函数与函数体在编译的时候就对应捆绑起来了,比如重载

动态绑定:按照主观意愿,使用一个函数名动态的调用很多同名的函数

4.2 虚函数

其实就是在父类中的函数声明最前面加上virtual关键词,从而在子类中可以重名改写这个函数,最后在相应的对象中通过指针或者引用调用响应对象的相同的函数,实现不同的自定义功能

  • 举个例子
    #include <iostream>
    using namespace std;
    
    class A
    {
    protected:
        int a;
    public:
        A() { a = 0; }
        virtual void Output()
        {
            cout << "A: " << a << endl;
        }
    };
    
    class B : public A
    {
    private:
        int b;
    public:
        B() : A() { b = 1; }
        void Output()
        {
            cout << "A: " << a << endl;
            cout << "B: " << b << endl;
        }
    };
    
    int main()
    {
        A obj_A;
        A *pA = &obj_A;
        pA->Output();
    
        B obj_B;
        pA = &obj_B;
        pA->Output();
    
        return 0;
    }
    
  • 输出
    C++课程学习记录
  • 如果删除基类中的virtual关键词,则无法调用子类对象中的Output函数,只会输出这个
    C++课程学习记录
  • 并且会警告
    C++课程学习记录

4.3 纯虚函数

其实就是提供一个接口,只有函数声明,不写任何函数定义,从而实现了一个从基类定义的接口,供所有的子类进行函数的重写以及后续主函数中的调用(通过指针)

唯一的语法注意点就是加一个=0,如下是一个示例

class A
{
private:
public:
    virtual void f() = 0;
    virtual void g();
    void h();
};
  • f()是一个纯虚函数,用来做基类中的接口的
  • g()是一个虚函数,需要有自己的定义
  • h()是一个普通的基类中的成员函数,在其派生类中不可以进行重写

4.4 抽象类

包含纯虚函数的类就是一个抽象类;如果类中全是纯虚函数,则这个类就是纯抽象类,如下是一个示例文章来源地址https://www.toymoban.com/news/detail-493002.html

class A
{
private:
public:
    virtual void f() = 0;
    virtual void g() = 0;
    virtual void h() = 0;
};

到了这里,关于C++课程学习记录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 课程学习前言

    app 抓包分析可以看到有签名有加固,毕竟需要 APK 去访问服务、获取数据,都需要 APK 有完整的信息,而这些信息、代码经过各种加密,还是放在 APK 里面。说白了,就是门锁紧了,钥匙藏在门口某个地方,也许就是地垫下面 逆向流程 拿到 App 应用的 apk ; 使用工具进行查壳

    2024年02月06日
    浏览(43)
  • XTuner 微调 课程学习

    大语言模型于海量的文本内容上,以无监督和半监督的方式进行训练的 模型微调的目的:使其在具体的使用场景或领域中输出更好的回答 增量预训练——给模型喂新的领域知识; 指令跟随或指令微调—— 基于海量的预训练数据训练出来的模型通常叫做——base模型或预训练

    2024年01月15日
    浏览(40)
  • STM32课程学习心得

                  在过去的几个月里,我投入了大量的时间和精力来深入学习STM32微控制器的相关知识。这段学习经历让我对这个领域有了更深入的理解,也让我有了更多的自信去面对实际工程的挑战。以下是我对这段学习经历的总结和心得体会。 在学习STM32的过程中,我主要

    2024年02月04日
    浏览(45)
  • 尚硅谷webpack课程学习笔记

    为什么需要使用打包工具? 开发时使用的框架、es6 语法 、less 等浏览器无法识别。 需要经过编译成浏览器能识别的css、js才可以运行。 打包工具可以帮我们编译,还可以做代码压缩、兼容处理、性能优化。 常见的打包工具有什么? vite、webpack、glup、grunt webapck最基本的使用

    2024年02月07日
    浏览(50)
  • C语言入门课程学习笔记-6

    本文学习自狄泰软件学院 唐佐林老师的 C语言入门课程,图片全部来源于课程PPT,仅用于个人学习记录 D,越界 C D 20 2 0 -1 A wrong 赋值越界 B str2[4]初始化为0元素 A wrong C AD strlen(s) ij j– 10 3 abc

    2024年04月28日
    浏览(36)
  • 机器学习基本概念(李宏毅课程)

    机器学习 ≈ 训练生成一个函数f(.) ,这个函数相当复杂。 例如: 机器学习的目的是寻找一个满足需求的函数f(.),但是具体使用什么方式寻找f(.)没有说明。 深度学习为机器学习领域的一个子领域,故深度学习给出了寻找函数的方法,即通过“神经网络”来训练生成一个函数

    2024年02月21日
    浏览(44)
  • 华为认证系统学习大纲及课程

    任何学习过程都需要一个科学合理的学习路线,才能够有条不紊的完成我们的学习目标。华为认证网络工程师所需学习的内容纷繁复杂,难度较大,所以今天特别为大家整理了一个全面的华为认证网络工程师学习大纲及课程,帮大家理清思路,攻破难关! HCIA 阶段面向零基础

    2024年02月09日
    浏览(41)
  • 1、中级机器学习课程简介

    本课程所需数据集夸克网盘下载链接:https://pan.quark.cn/s/9b4e9a1246b2 提取码:uDzP 欢迎来到机器学习中级课程! 如果你对机器学习有一些基础,并且希望学习如何快速提高模型质量,那么你来对地方了!在这门课程中,你将通过学习如何: 处理在真实世界数据集中经常出现的数

    2024年01月21日
    浏览(45)
  • 《Kubernetes入门实战课》课程学习笔记(一)

    现在 Kubernetes 已经没有了实际意义上的竞争对手,它的地位就如同 Linux 一样,成为了事实上的云原生操作系统,是构建现代应用的基石。 现代应用是什么? 是微服务,是服务网格,这些统统要围绕着容器来开发、部署和运行。 使用容器就必然要用到容器编排技术,在现在只

    2024年02月17日
    浏览(72)
  • 《MySQL 实战 45 讲》课程学习笔记(四)

    索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。 哈希表 哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位

    2024年02月14日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包