0%

360 笔试知识点记录 3.24

对于笔试过程中的不太清晰的部分知识点做一下整理

C++继承:私有,保护,公有

private, protected, public的访问范围:

private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数

类的继承后方法属性变化:

使用private继承,基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
使用protected继承,基类的所有公有成员和保护成员都成为派生类的保护成员。
使用public继承,基类的公有成员和保护成员保持原有的状态。
注:

  • 无论何种继承方式,基类中的private成员对派生类都是不可见的
  • 继承属性变化影响的是派生类的对象或者派生类的派生类

此外,还存在另外一种机制:
我们已经知道,在基类以private方式被继承时,其public和protected成员在子类中变为private成员。然而某些情况下,需要在子类中将一个或多个继承的成员恢复其在基类中的访问权限。
C++支持两种方式实现该目的:

  • 方法一,使用 using 语句,这是C++标准建议使用的方式
  • 方法二,使用访问声明,形式为 base-class::member;

注,只能恢复原有访问权限,而不能提高或降低访问权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用一个using声明来指出 Student 的对象或者派生类可以使用特定的基类成员,即使采用的是私有派生。
//派生类只能为那些它可以访问的名字提供using声明或访问声明。
//注意:using声明只是用成员名,没有圆括号、函数特征标和返回类型。
class people {
public:
string name;
};
class Student : private people {
...
public:
using people::name; //方法一
people::name; //方法二
...
};

参考链接:
C++继承:公有,私有,保护

C++继承:派生类的构造函数(构造函数的执行顺序)

单继承时,派生类构造函数总是先调用基类构造函数再执行其他代码

基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承
在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。这种矛盾在 C++ 继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <string>
using namespace std;

//基类People
class People {
protected:
string m_name;
int m_age;
public:
People(string name, int age) : m_name(name), m_age(age) {}
};

//派生类Student
class Student : public People {
private:
float m_score;
public:
//People(name, age)就是显式调用基类的构造函数
Student(string name, int age, float score) : People(name, age), m_score(score) {}
void display();
};

void Student::display() {
cout << m_name << "的年龄是" << m_age << ",成绩是" << m_score << "。" << endl;
}

int main() {
Student stu("小明", 16, 90.5);
stu.display();
return 0;
}

注意第 20 行代码,People(name, age) 就是调用基类的构造函数,并将 name 和 age 作为实参传递给它,m_score(score) 是派生类的参数初始化表,它们之间以逗号,隔开。
也可以将基类构造函数的调用放在参数初始化表后面,但是不管它们的顺序如何,派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码)。
派生类的构造函数中,省略基类构造函数时,派生类的构造函数,自动调用基类的默认构造函数。
也就是说构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的
派生类的析构函数被执行的时候,执行完派生类的析构函数后,自动调用基类的析构函数。
还有一点要注意,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的

多继承下的构造函数

1
2
3
class D : public A, private B, protected C {
//类D新增加的成员
}

D 是多继承形式的派生类,它以公有的方式继承 A 类,以私有的方式继承 B 类,以保护的方式继承 C 类。D 根据不同的继承方式获取 A、B、C 中的成员,确定它们在派生类中的访问权限。
多继承形式下的构造函数和单继承形式基本相同,只是要在派生类的构造函数中调用多个基类的构造函数。
基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。即使将 D 类构造函数写作下面的形式,那么也是先调用 A 类的构造函数,再调用 B 类构造函数,最后调用 C 类构造函数。

1
2
3
D(形参列表) : B(实参列表), C(实参列表), A(实参列表) {
//其他操作
}

虚继承时的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
class A {  //定义基类A
A(int i) { } //基类构造函数,有一个参数
};
class B : virtual public A { //A作为B的虚基类
B(int n) : A(n) { } //B类构造函数,在初始化表中对虚基类初始化
};
class C : virtual public A { //A作为C的虚基类
C(int n) : A(n) { } //C类构造函数,在初始化表中对虚基类初始化
};
class D : public B, public C {
D(int n) : A(n), B(n), C(n) { } //类D的构造函数,在初始化表中对所有基类初始化
};

在定义类 D 的构造函数时,与以往使用的方法有所不同。规定:
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++ 编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类 B 和类 C )对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

参考链接:
C++派生类的构造函数(构造函数的执行顺序)

C++继承:基类对象与派生类对象的使用关系

派生类对象作为基类对象处理

由于派生类具有所有基类的成员,所以把派生类的对象赋给基类对象是合理的,使用这种方式只会将基类对象成员赋值,不过要求这种继承方式必须是 public 方式。但是,反过来赋值会使基类中具有派生类的成员(因为派生类的成员通常是比基类的成员多),所以这是不允许的。

1
2
3
4
People a;  // 基类对象
Student b; // 派生类对象
a = b; // 可以
b = a; // 不可以

基类指针指向派生类对象

因为派生类对象也是基类对象,所以指向派生类对象的指针可以转换为指向基类对象的指针,这种引用方式是安全的。但是用这种方式基类指针只能操作基类中的成员。如果试图通过基类指针引用那些只有在派生类中才有的成员,编译系统会报告错误。

1
2
3
4
People a;  // 基类对象
Student b; // 派生类对象
People* pa = &b; // 可以
Student* pb = &a; // 不可以

基类引用作为派生类的别名

基类的引用可以作为派生类对象的别名,使用这种方式基类引用只能访问基类成员。但是反过来则不行,派生类的引用不可以作为基类对象的别名。

1
2
3
4
People a;  // 基类对象
Student b; // 派生类对象
People &pa = b; // 可以
Student &pb = a; // 不可以

派生类指针强制指向基类对象

直接用派生类指针指向基类的对象,这种方式会导致语法错误。如果可以确定从基类到派生类的转换是安全的,可以将派生类指针强制指向基类对象,通过这个“强制指向基类的派生类指针”访问的函数依然是派生类的成员函数。这种强制转换使用的静态转型运算符,其使用格式如下:
派生类对象指针=static_cast<派生类*>(&基类对象);

1
2
3
People a;  // 基类对象
Student b; // 派生类对象
Student* pb = static_cast<Student*>(&a); // 可以,但不安全

基类引用也可以强制转换为派生类引用。将基类指针强制转换为派生类指针,或将基类引用强制转换为派生类引用,都有安全隐患。

参考链接:
基类对象与派生类对象的使用关系
c++派生类对象赋值给基类对象
C++派生类对象和基类对象赋值
基类指针和派生类指针之间的转换
C++ 基类指针和派生类指针之间的转换

存储过程

参考链接:
SQL存储过程使用介绍

建造者模式

参考链接:
图说设计模式

编程题

image-20200325173447064

图片备注:图源网络侵删

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//思路就是dp(n,m) = Pa中 + Pa没中 * Pb没中 * Pb销毁的也没中 * dp(n, m-3) + Pa没中 * Pb没中 * Pb销毁的中了 * dp(n-1, m-2)
//因为出现了m-1 m-2 n-1,所以临界情况要处理一下

#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

int main() {
int n = 0, m = 0;
while (cin >> n >> m) {
vector<vector<double>> dp(n + 1, vector<double>(m + 1, 0.0));
for (int i = 1; i <= n; ++i) {
double nf = i;
dp[i][0] = 1.0;
dp[i][1] = nf / (nf + 1.0);
dp[i][2] = nf / (nf + 2.0) + 2.0/(nf+2.0) * 1.0/(nf+1.0) * 1.0 * dp[i - 1][0];
}
for (int i = 1; i <= n; ++i)
for (int j = 3; j <= m; ++j) {
double nf = i, mf = j;
dp[i][j] = nf/(nf+mf) + mf/(nf+mf) * (mf-1.0)/(nf+mf-1.0) * (mf-2.0)/(mf+nf-2.0) * dp[i][j - 3]
+ mf/(nf+mf) * (mf-1.0)/(nf+mf-1.0) * nf/(mf+nf-2.0) * dp[i - 1][j - 2];
}
cout << fixed << setprecision(4) << dp[n][m] << endl; //格式化输出
}
return 0;
}

参考链接:
iomanip_百度百科
360笔试代码题2AC
360笔试题编程题