第19章 模板和泛型编程 代码重用的概念 代码重用是指在不同的程序或项目中使用已有的代码,而不是重新编写相同的功能。C++提供了多种代码重用的机制,包括函数重载、模板、继承、组合等。
继承与组合 继承 继承是一种”is-a”关系,派生类是基类的一种特殊类型。
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 class Animal {protected : std::string name; public : Animal (const std::string& n) : name (n) {} void eat () { std::cout << name << " is eating." << std::endl; } virtual void makeSound () { std::cout << name << " makes a sound." << std::endl; } }; class Dog : public Animal {public : Dog (const std::string& n) : Animal (n) {} void bark () { std::cout << name << " is barking." << std::endl; } void makeSound () override { bark (); } };
组合 组合是一种”has-a”关系,一个类包含另一个类的对象作为成员。
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 33 34 35 class Engine {private : int horsepower; public : Engine (int hp) : horsepower (hp) { std::cout << "Engine created with " << hp << " HP" << std::endl; } void start () { std::cout << "Engine started" << std::endl; } void stop () { std::cout << "Engine stopped" << std::endl; } }; class Car {private : std::string model; Engine engine; public : Car (const std::string& m, int hp) : model (m), engine (hp) { std::cout << "Car created: " << model << std::endl; } void drive () { engine.start (); std::cout << model << " is driving" << std::endl; } void park () { std::cout << model << " is parking" << std::endl; engine.stop (); } };
继承与组合的比较 特性 继承 组合 关系 is-a has-a 代码重用 继承基类的行为 复用组件的功能 灵活性 较低,强耦合 较高,松耦合 可维护性 较低,修改基类可能影响派生类 较高,组件独立 扩展性 较低,受限于继承层次 较高,易于替换组件
私有继承 私有继承是一种实现代码重用的方式,它建立了一种”is-implemented-in-terms-of”(是根据…实现的)关系。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class StringRep {private : char * data; size_t length; public : StringRep (const char * str) { length = strlen (str); data = new char [length + 1 ]; strcpy (data, str); } ~StringRep () { delete [] data; } const char * getCString () const { return data; } size_t getLength () const { return length; } }; class String {private : class Impl : private StringRep { public : Impl (const char * str) : StringRep (str) {} using StringRep::getCString; using StringRep::getLength; }; Impl* impl; public : String (const char * str) { impl = new Impl (str); } ~String () { delete impl; } const char * c_str () const { return impl->getCString (); } size_t length () const { return impl->getLength (); } };
保护继承 保护继承是另一种实现代码重用的方式,它建立了一种”is-implemented-in-terms-of”关系,但允许派生类访问基类的保护成员。
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 33 class Base {protected : int value; public : Base (int v) : value (v) {} void print () { std::cout << "Base::value = " << value << std::endl; } }; class Derived : protected Base {private : int derivedValue; public : Derived (int v, int dv) : Base (v), derivedValue (dv) {} void printDerived () { std::cout << "Base::value = " << value << std::endl; std::cout << "Derived::derivedValue = " << derivedValue << std::endl; } }; class FurtherDerived : public Derived {public : FurtherDerived (int v, int dv) : Derived (v, dv) {} void printFurther () { std::cout << "Base::value = " << value << std::endl; } };
C++20新特性:模板增强 C++20对模板系统进行了多项增强,包括模板lambda、requires表达式等:
模板Lambda C++20允许在lambda表达式中使用模板参数:
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 #include <iostream> int main () { auto add = []<typename T>(T a, T b) { return a + b; }; std::cout << "Add integers: " << add (1 , 2 ) << std::endl; std::cout << "Add doubles: " << add (1.5 , 2.5 ) << std::endl; std::cout << "Add strings: " << add (std::string ("Hello" ), std::string (" World" )) << std::endl; auto multiply = []<typename T>(T a, T b) requires std::is_arithmetic_v<T> { return a * b; }; std::cout << "Multiply integers: " << multiply (3 , 4 ) << std::endl; std::cout << "Multiply doubles: " << multiply (2.5 , 4.0 ) << std::endl; auto print = []<typename ... Args>(Args&&... args) { (std::cout << ... << args) << std::endl; }; print ("The answer is " , 42 , ", and pi is " , 3.14159 ); return 0 ; }
带requires表达式的模板 C++20引入了requires表达式,用于在模板中添加约束:
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 33 34 35 36 #include <concepts> #include <iostream> template <typename T> requires std::integral<T> || std::floating_point<T> T absolute (T value) { return value < 0 ? -value : value; } template <std::integral T> T square (T value) { return value * value; } template <typename T> requires std::copyable<T> && requires (T a, T b) { { a + b } -> std::same_as<T>; { a * b } -> std::same_as<T>; } T sum_and_product (T a, T b) { return a + b * a; } int main () { std::cout << "Absolute value of -5: " << absolute (-5 ) << std::endl; std::cout << "Absolute value of -3.14: " << absolute (-3.14 ) << std::endl; std::cout << "Square of 5: " << square (5 ) << std::endl; std::cout << "Sum and product of 3 and 4: " << sum_and_product (3 , 4 ) << std::endl; return 0 ; }
C++20模板增强的优点 更简洁的语法 :模板lambda简化了泛型lambda的写法更强的表达能力 :requires表达式提供了更灵活的模板约束方式更好的错误信息 :当模板参数不满足约束时,提供更清晰的错误信息更高的代码可读性 :使用概念和requires表达式使模板代码更易于理解更好的编译性能 :编译器可以更早地检查模板参数的有效性模板 模板是C++中实现泛型编程的重要机制,它允许编写与类型无关的代码。
函数模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <typename T>T max (T a, T b) { return (a > b) ? a : b; } template int max <int >(int , int );template double max <double >(double , double );int main () { int i = max (10 , 20 ); double d = max (3.14 , 2.71 ); std::string s = max (std::string ("hello" ), std::string ("world" )); std::cout << "Max int: " << i << std::endl; std::cout << "Max double: " << d << std::endl; std::cout << "Max string: " << s << std::endl; return 0 ; }
类模板 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 33 34 35 36 37 38 39 40 41 42 43 44 45 template <typename T>class Stack {private : std::vector<T> elements; public : void push (const T& item) { elements.push_back (item); } void pop () { if (!empty ()) { elements.pop_back (); } } T& top () { return elements.back (); } bool empty () const { return elements.empty (); } size_t size () const { return elements.size (); } }; int main () { Stack<int > intStack; intStack.push (10 ); intStack.push (20 ); std::cout << "Top: " << intStack.top () << std::endl; intStack.pop (); std::cout << "Top after pop: " << intStack.top () << std::endl; Stack<std::string> stringStack; stringStack.push ("hello" ); stringStack.push ("world" ); std::cout << "Top: " << stringStack.top () << std::endl; stringStack.pop (); std::cout << "Top after pop: " << stringStack.top () << std::endl; return 0 ; }
模板特化 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 33 34 35 36 37 38 template <typename T>class MyClass {public : void print () { std::cout << "General template" << std::endl; } }; template <>class MyClass <int > {public : void print () { std::cout << "Specialized for int" << std::endl; } }; template <>class MyClass <std::string> {public : void print () { std::cout << "Specialized for string" << std::endl; } }; int main () { MyClass<double > d; MyClass<int > i; MyClass<std::string> s; d.print (); i.print (); s.print (); return 0 ; }
标准模板库(STL) STL是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 33 34 35 36 37 38 #include <vector> #include <list> #include <map> #include <set> #include <unordered_map> #include <unordered_set> int main () { std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; vec.push_back (6 ); std::list<int > lst = {1 , 2 , 3 , 4 , 5 }; lst.push_back (6 ); lst.push_front (0 ); std::map<std::string, int > map; map["one" ] = 1 ; map["two" ] = 2 ; std::set<int > set = {1 , 2 , 3 , 4 , 5 }; set.insert (6 ); std::unordered_map<std::string, int > umap; umap["one" ] = 1 ; umap["two" ] = 2 ; std::unordered_set<int > uset = {1 , 2 , 3 , 4 , 5 }; uset.insert (6 ); return 0 ; }
算法 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 33 #include <algorithm> #include <vector> int main () { std::vector<int > vec = {5 , 2 , 8 , 1 , 9 , 3 }; std::sort (vec.begin (), vec.end ()); auto it = std::find (vec.begin (), vec.end (), 5 ); if (it != vec.end ()) { std::cout << "Found 5 at position: " << std::distance (vec.begin (), it) << std::endl; } int count = std::count (vec.begin (), vec.end (), 5 ); std::cout << "Count of 5: " << count << std::endl; auto maxIt = std::max_element (vec.begin (), vec.end ()); std::cout << "Max element: " << *maxIt << std::endl; auto minIt = std::min_element (vec.begin (), vec.end ()); std::cout << "Min element: " << *minIt << std::endl; std::reverse (vec.begin (), vec.end ()); return 0 ; }
迭代器 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 #include <vector> int main () { std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; std::cout << "Forward iteration: " << std::endl; for (auto it = vec.begin (); it != vec.end (); ++it) { std::cout << *it << " " ; } std::cout << std::endl; std::cout << "Reverse iteration: " << std::endl; for (auto it = vec.rbegin (); it != vec.rend (); ++it) { std::cout << *it << " " ; } std::cout << std::endl; std::cout << "Constant iteration: " << std::endl; for (auto it = vec.cbegin (); it != vec.cend (); ++it) { std::cout << *it << " " ; } std::cout << std::endl; return 0 ; }
函数对象 函数对象是重载了函数调用运算符()的类的实例,可以像函数一样被调用。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Add {private : int value; public : Add (int v) : value (v) {} int operator () (int x) const { return x + value; } }; class Multiply {private : int factor; public : Multiply (int f) : factor (f) {} int operator () (int x) const { return x * factor; } }; int main () { Add add5 (5 ) ; int result1 = add5 (10 ); std::cout << "10 + 5 = " << result1 << std::endl; Multiply multiply3 (3 ) ; int result2 = multiply3 (10 ); std::cout << "10 * 3 = " << result2 << std::endl; std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; std::transform (vec.begin (), vec.end (), vec.begin (), add5); std::cout << "After adding 5: " << std::endl; for (int num : vec) { std::cout << num << " " ; } std::cout << std::endl; return 0 ; }
Lambda 表达式(C++11+) Lambda表达式是C++11引入的匿名函数对象,提供了一种简洁的方式来定义函数。
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 int main () { auto add = [](int a, int b) { return a + b; }; std::cout << "5 + 3 = " << add (5 , 3 ) << std::endl; int x = 10 ; auto addX = [x](int a) { return a + x; }; std::cout << "5 + x = " << addX (5 ) << std::endl; auto addXRef = [&x](int a) { return a + x; }; auto addAll = [=](int a) { return a + x; }; auto increment = [x]() mutable { return ++x; }; std::vector<int > vec = {5 , 2 , 8 , 1 , 9 , 3 }; std::sort (vec.begin (), vec.end (), [](int a, int b) { return a > b; }); std::cout << "Sorted in descending order: " << std::endl; for (int num : vec) { std::cout << num << " " ; } std::cout << std::endl; return 0 ; }
代码重用的最佳实践 1. 选择合适的代码重用机制 继承 :适用于”is-a”关系,需要多态的场景组合 :适用于”has-a”关系,需要松耦合的场景模板 :适用于泛型编程,需要与类型无关的代码函数对象 :适用于需要状态的函数Lambda表达式 :适用于简短的、一次性的函数2. 遵循设计原则 单一职责原则 :每个类只负责一项功能开放-封闭原则 :对扩展开放,对修改封闭里氏替换原则 :子类应该能够替换父类接口隔离原则 :客户端不应该依赖它不使用的接口依赖倒置原则 :依赖于抽象,而不是具体实现3. 合理使用STL 选择合适的容器 :根据访问模式和性能需求选择容器使用算法 :优先使用STL算法而不是手写循环使用迭代器 :通过迭代器访问容器元素使用函数对象和lambda :简化代码,提高可读性4. 代码组织 模块化 :将相关功能组织到模块中命名空间 :使用命名空间避免名称冲突头文件和源文件分离 :声明和实现分离文档 :为代码添加注释和文档常见错误和陷阱 1. 继承滥用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person {public : void eat () {} void sleep () {} }; class Student : public Person {public : void study () {} }; class Student {private : Person person; public : void eat () { person.eat (); } void sleep () { person.sleep (); } void study () {} };
2. 模板错误 1 2 3 4 5 6 7 8 9 10 11 12 template <typename T>class MyClass {}; template <>class MyClass <int > {};
3. STL使用错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; for (auto it = vec.begin (); it != vec.end (); ++it) { if (*it == 3 ) { vec.erase (it); } } std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; for (auto it = vec.begin (); it != vec.end ();) { if (*it == 3 ) { it = vec.erase (it); } else { ++it; } }
4. 函数对象错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Comparator {public : bool operator () (int a, int b) { return a < b; } }; class Comparator {public : bool operator () (int a, int b) const { return a < b; } };
小结 本章介绍了C++中的代码重用机制,包括:
继承与组合 :两种基本的代码重用方式,分别对应”is-a”和”has-a”关系私有继承和保护继承 :实现”is-implemented-in-terms-of”关系模板 :实现泛型编程,编写与类型无关的代码标准模板库(STL) :提供容器、算法、迭代器等工具函数对象 :重载函数调用运算符的类的实例Lambda表达式 :C++11引入的匿名函数对象代码重用的最佳实践 :选择合适的机制、遵循设计原则、合理使用STL等常见错误和陷阱 :继承滥用、模板错误、STL使用错误等代码重用是提高开发效率、减少代码冗余、提高代码质量的重要手段。通过合理使用C++提供的代码重用机制,可以编写出更简洁、更高效、更可维护的代码。在实际编程中,应根据具体情况选择合适的代码重用方式,遵循最佳实践,避免常见错误。
在后续章节中,我们将学习C++的其他高级特性,如异常处理、命名空间、模板特化等,进一步提高C++编程能力。