C++中的代码重用
C++中的代码重用
valarray类简介
头文件valarray
与vector和array比起来, valarray的功能更加强大。valarray的算术支持更多。
valarray <int> v1 ; // int类型的数组,大小为0 valarray <int> v2(8); // 8个Int元素的数组 valarray <double> v3(10,8); // 8个10 double gra[5] = {1.6,12.6,.9,.09,9.99}; valarray<double> v4(gra, 4); // 4个元素的数组, 取gra数组的前4个元素
C++ 11中也可以使用初始化列表
valarry<int> v5 = {1,2,3};
valarry<int> v5 {1,2,3};
valarray类的一些方法
has-a的关系表示组合
示例:
Student.h文件
#ifndef STUDENT_H_ #define STUDENT_H_ #include <iostream> #include <string> #include <valarray> class Student { private: typedef std::valarray<double> ArrayDb; std::string name; ArrayDb scores; std::ostream & arr_out(std::ostream & os) const; public: Student():name{"Null Student"},scores(){} explicit Student(int n):name{"Nully"},scores(n){} Student(const std::string &s , const ArrayDb & a):name(s),scores(a) {}; Student(const char * str, const double * pd, int n):name(str), scores(pd,n){}; ~Student(); double average() const; const std::string &Name() const; double &operator[](int i); // []运算符重载 double operator[](int i) const; // 友元函数 // 输入 friend std::istream & operator>> (std::istream &is, Student & stu); // 1 word friend std::istream & getline(std::istream & is, Student & stu); // 1 line // 输出 friend std::ostream &operator <<(std::ostream & os, const Student & stu); }; #endif
说明: typedef std::valarray<double> ArrayDb;
放在类内的私有部分,表示该可以在Student类的实现中使用它,而不能在Student类外使用。
关于隐式类型转换的1点说明:
#include <iostream> using std::cout; using std::endl; class Test { private: int int_num; public: Test():int_num(0) { cout << "default constructor" << endl; }; Test(int n):int_num(n) { cout << "1 param constructor" << endl; } ~Test() { cout << "destroy test obj..." << endl; }; Test(const Test &t) { cout << "copy constructor" << endl; int_num = t.int_num; } int getNum() const { return int_num ; } }; int main(int argc, char const *argv[]) { Test test(10); cout<< "Output int_num value:" << test.getNum() << endl; test = 11; // 调用构造函数Test(11)生成零时的Test对象, 使用临时对象来替换原来的test return 0; }
一个参数的构造函数,没有使用explicit修饰
Test(int n):int_num(n) { cout << "1 param constructor" << endl; }
test中的值变成临时对象中的值,test地址没变(实验论证,并没有调用拷贝构造函数)
Test test(10); cout<< "Output int_num value:" << test.getNum() << endl; test = 11; // 调用构造函数Test(11)生成零时的Test对象, 使用临时对象来替换原来的test
对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。 对于成员对象,构造函数则使用成员名。
Student(const char * str, const double * pd, int n):name(str), scores(pd,n){};
初始化列表中每一项都调用与之匹配的构造函数。即name(str)调用构造函数string(const char *), scores(pd,n)调用构造函数ArrayDb(const double * , int);
C++ 要求在构建对象其他部分之前, 先构建继承对象的所有成员对象, 如果省略初始化列表, C++将使用成员对象所属类的默认构造函数。
初始化顺序:初始化列表中包含多个项目时吗, 初始化顺序为成员被声明的顺序。而不是在初始化列表中的顺序。
Student.cpp文件
#include "student.h" using std::endl; using std::istream; using std::ostream; using std::string; // double Student::average() const { if(scores.size() > 0) { return scores.sum() / scores.size(); }else return 0; } inline const std::string & Student::Name() const { return name; } inline double & Student::operator[](int i) { return scores[i]; } inline double Student::operator[](int i) const { return scores[i]; } // 私有方法 std::ostream & Student::arr_out(std::ostream & os) const { int i ; int lim = scores.size(); if(lim > 0) { for(i= 0; i< lim ; i++) { os << scores[i] << " " ; if(i % 5 != 0) { os << endl; } } } else { os << " empty array" ; } } // friends std::istream & operator>> (std::istream &is, Student & stu) { is >> stu.name; return is; } std::istream & getline(std::istream & is, Student & stu) { getline(is,stu.name); return is; } std::ostream &operator <<(std::ostream & os, const Student & stu) { os <<"Scores for " << stu.name << endl; stu.arr_out(os); return os; }
use_stu.cpp文件
#include "student.h" // #include <iostream> using std::cout; using std::endl; using std::cin; void set(Student & sa, int n); const int pupils = 3; const int quizzes = 5; int main(int argc, char const *argv[]) { Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)}; int i ; for(i = 0; i< pupils; i++) { set(ada[i], quizzes); } cout << "\n Students List: \n"; for(i = 0; i < pupils; i++) { cout << endl << ada[i]; cout << "average: " << ada[i].average() << endl; } cout << "Done!" ; return 0; } void set(Student & sa, int n) { cout << "Please Enter the Student's name: "; getline(cin,sa); cout << "Please enter" << n << " quiz scores: \n"; for(int i = 0; i < n ; i++) { cin >> sa[i]; } while(cin.get() != '\n') { continue; } }
私有继承
包含(组合)是将对象作为一个命名的成员对象添加到类中,而私有继承(private inheritance)将对象作为一个未命名的继承对象添加到类中。
class Student :private std::string, private std::valarray<double> { }// 私有继承提供了两个无名称的子对象成员
初始化问题:
对于继承类, 新版本的构造函数将使用成员初始化列表语法, 它使用类名而不是成员名来标识构造函数:
Student(const char *str, const double *pd, int n) :std::string(str),ArrayDb(pd,n)
student1.h头文件
#ifndef STUDENT1_H_ #define STUDENT1_H_ #include <iostream> #include <string> #include <valarray> class Student : private std::string, private std::valarray<double> { private: typedef std::valarray<double> ArrayDb; // 输出成绩的私有方法 std::ostream &arr_out(std::ostream &os) const; public: Student() : std::string("Null Student"), ArrayDb(){}; explicit Student(const std::string &s) : std::string(s), ArrayDb(){}; explicit Student(int n) : std::string("Nully"), ArrayDb(n) {} Student(const std::string & s, int n): std::string(s), ArrayDb(n) {} Student(const std::string & s, const double *pd,int n): std::string(s), ArrayDb(pd,n) {} ~Student(){}; double average() const; double & operator[](int i); double operator[](int i) const; const std::string & name() const; // 友元函数 friend std::istream & operator>> (std::istream &is, Student & stu); // 1 word friend std::istream & getline (std::istream &is, Student & stu); // 1 line // 输出 friend std::ostream & operator<< (std::ostream & os, const Student & stu); }; #endif
访问基类的方法:
使用私有继承时,只能在子类的方法中使用父类的方法。
使用组合时将使用对象名来调用方法, 而使用私有继承时将使用类名和作用域解析运算符来调用方法。
使用私有继承时,子类没有父类对象的名称, 那么如何访问父类对象呢?
答: 进行强制类型转换。
const string & Student::Name() const { return (const string &) *this; }
访问基类的友元函数
友元函数不属于类成员。 然而,可以通过显式地转换为基类来调用正确的函数。
ostream & operator<<(ostream & os , Student & stu) { os << "Scores for " << (const string &)stu << ": \n" ; }
在私有继承中,未进行显式类型转换的派生类引用或者指针, 是无法赋值给基类的引用或指针的。原因1: 如果不转换, 将进入无限的递归调用; 原因2,如果不转换, 因为类是调用的多重继承, 无法指定调用哪个基类的方法。
私有继承中必须使用基类的类名作为作用域来访问基类中的方法 base::method();student1.cpp
#include "student1.h" using std::endl; using std::istream; using std::ostream; using std::string; double Student::average() const { if (ArrayDb::size() > 0) { return ArrayDb::sum() / ArrayDb::size(); } else return 0; } double &Student::operator[](int i) { return ArrayDb::operator[](i); } double Student::operator[](int i) const { return ArrayDb::operator[](i); } const std::string &Student::name() const { return (const string &)*this; } std::ostream &Student::arr_out(std::ostream &os) const { int i; int lim = ArrayDb::size(); if (lim > 0) { for (i = 0; i < lim; i++) { os << ArrayDb::operator[](i) << " "; if (i % 5 == 4) { os << endl; } if (i % 5 == 0) { os << endl; } } } else os << "Empty array!" << endl; return os; } std::istream &operator>>(std::istream &is, Student &stu) { is >> (string &)stu; return is; } std::ostream &operator<<(std::ostream &os, const Student &stu) { os << "Scores for" << (const string &)stu; stu.arr_out(os); return os; } std::istream & getline (std::istream &is, Student & stu) { getline(is,(string &)stu); return is; }
main文件:
#include "student1.h" // #include <iostream> using std::cout; using std::endl; using std::cin; void set(Student & sa, int n); const int pupils = 3; const int quizzes = 5; int main(int argc, char const *argv[]) { Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)}; int i ; for(i = 0; i< pupils; i++) { set(ada[i], quizzes); } cout << "\n Students List: \n"; for(i = 0; i< pupils ;i++) { cout << ada[i].name() << endl; } for(i = 0; i < pupils; i++) { cout << endl<< ada[i]; cout << "average: " << ada[i].average() << endl; } cout << "Done!" ; return 0; } void set(Student & sa, int n) { cout << "Please Enter the Student's name: "; getline(cin,sa); cout << "Please enter" << n << " quiz scores: \n"; for(int i = 0; i < n ; i++) { cin >> sa[i]; } while(cin.get() != '\n') { continue; } }
上述代码的一点说明:
double & operator[](int i); // 1 double operator[](int i) const; // 2
这两个语句 咋一看是不能重载的, 而在实际操作过程中是可以发生重载的。比如说,
Student s1(3); s1[0]; // 调用 第1条语句 const Student s2(3); s2[0]; // 调用语句 2
说明const 也能作为函数重载的调用规则。
组合和私有继承的区别:
- 是否显式使用成员
- 列表初始化
- 调用方法时是否添加作用域
使用包含还是继承?
优先考虑组合。
使用私有继承的情况:
- 相应访问 某个类的protected成员。 使用组合时访问不了的, 但使用私有继承可以直接访问。
- 重新定义虚函数。 派生类可以重新定义虚函数。 使用私有继承的话, 重新定义的虚函数只能在类中使用, 而不是公有的。
总结: 通常,应该使用组合来建立has-a关系, 如果新类需要访问原有类的保护成员,或者需要重新定义虚函数,则应该使用私有继承。
使用保护继承
class Student :protected std::string, protected std::valarray<double> { }
使用保护继承时,父类的公有成员和保护成员都会成为子类的保护成员。
隐式向上转换(implicit upcasting)意味着无需进行显式类型转换, 就可以将基类指针或引用指向派生类对象。
各种继承方式
特征 | 公有(public)继承 | 保护(protected)继承 | 私有(private)继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类的接口访问 | 只能通过基类的接口访问 |
能否向上隐式转换 | 是 | 是(但只能在派生类中) | 否 |
使用using重新定义访问权限
方法1:定义一个公有函数,包含一个私有函数。
方法2:在子类的公有部分使用using 声明,声明要使用的父类成员。
class Student :protected std::string, protected std::valarray<double> { public: using std::valarray<double>::min; using std::valarray<double>::max; }
上述声明使得valarray
using声明只适用于继承,而不适用于包含。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?