这篇博客是用来记录期中考试过后我对C++的课上复现以及课后练习的过程
- 知识点请食用目录进行参考
- 未涉及的知识点请食用我在之前写过の一篇期中机考总结
传送门:C++期中机考试题
#include
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;
}
#include
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
伪代码:(中序遍历为例)
---------------------
传入结点
if (结点为空) return;
传入结点的左指针;
输出;
传入结点的右指针;
赠送一道算法题
AcWing 3384. 二叉树遍历
AC代码
#include
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;
}
参考书籍:《C++面向对象程序设计》 ——科学出版社
#include
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
类与上述一致
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
,那么
ptr
所属对象A的结点开始递归搜索到B结点#include
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
数据成员只可以在这个类中使用,其他地方包括它的派生类都不可以访问就是没啥区别,这是主要工程上主要使用的继承方式
可以理解为
- 将基类的公有与保护的成员全部变成私有类型了,类外都不能访问到基类的
public
成员了- 将基类的公有与保护的成员作为自己的私有成员继承下来了,后续的派生类都没有访问的权限
- 可以理解为将基类的公有的成员全部变成保护类型了,类外同样都不能访问到基类的
public
成员了- 将基类的公有成员作为自己的保护成员继承下来了,后续的派生类任然可以访问将基类的
public
成员作为protected
成员进行访问
只是在派生类中对继承下来的成员进行了继承方式的修改,并不影响基类中成员本身的权限属性
举个例子
#include
#include
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
#include
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;
}
没什么好多说的,上代码!代码看懂了,输出结果看懂了,就OK了
唯一需要强调的一点是,在多级派生时,对于一个派生类对象的初始化,一定要附带着把他所有的“爹”也全部初始化,否则调用默认构造函数初始化他的所有“爹”
#include
#include
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
方法,当然自己的什么都可以调用啦~
也就是一个派生类继承了 ≥ 2 \ge2 ≥2个基类
当前对象的成员如果与基类的成员重名或者函数重名并且参数列表完全一致,就会被当前的派生类完全覆盖,以下为变量名覆盖
程序
#include
#include
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
修改后的类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
无论虚基类出现在继承层次的哪个位置上,他们都是在非虚基类之前被构造。有虚基类的派生类的构造函数的调用次序是:编译器按照直接基类的声明次序,检查虚基类的出现情况,每个继承子树按照深度优先的顺序检查并调用虚基类的构造函数。虚基类多次出现只调用一次构造函数。虚基类的构造函数调用完之后,再按照声明的顺序调用非虚基类的构造函数(真的是又臭又长)
总结一下就是两点:
- 按照声明顺序,深度优先搜索虚基类,后序构造
- 按照声明顺序,深度优先搜索非虚基类,后序构造
举三个例子就知道了(图中虚继承用虚线表示)
例一:
class A { };
class B : public virtual A { };
class C : public virtual A { };
class D : public B, public 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 { };
那么
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 {};
那么
F
对象创建时,构造函数的调用顺序是:D, C, A, B, E, F
大概就是对于一个派生类,在赋值 or 指针传递 or 引用传递给一个基类对象 or 基类指针 or 基类引用时,会失去派生类的另外加的成员变量,变成一个基类的对象。为了通过基类的指针,改变派生类的成员方法,引出了C++中的一大特性:多态性
先补充一下函数调用绑定的知识点:分为静态绑定与动态绑定
静态绑定:函数与函数体在编译的时候就对应捆绑起来了,比如重载
动态绑定:按照主观意愿,使用一个函数名动态的调用很多同名的函数
其实就是在父类中的函数声明最前面加上
virtual
关键词,从而在子类中可以重名改写这个函数,最后在相应的对象中通过指针或者引用调用响应对象的相同的函数,实现不同的自定义功能
#include
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;
}
virtual
关键词,则无法调用子类对象中的Output
函数,只会输出这个其实就是提供一个接口,只有函数声明,不写任何函数定义,从而实现了一个从基类定义的接口,供所有的子类进行函数的重写以及后续主函数中的调用(通过指针)
唯一的语法注意点就是加一个
=0
,如下是一个示例
class A
{
private:
public:
virtual void f() = 0;
virtual void g();
void h();
};
f()
是一个纯虚函数,用来做基类中的接口的g()
是一个虚函数,需要有自己的定义h()
是一个普通的基类中的成员函数,在其派生类中不可以进行重写包含纯虚函数的类就是一个抽象类;如果类中全是纯虚函数,则这个类就是纯抽象类,如下是一个示例
class A
{
private:
public:
virtual void f() = 0;
virtual void g() = 0;
virtual void h() = 0;
};