二进制文件读写
二进制文件是一种以二进制编码形式存储数据的文件,与文本文件不同,二进制文件不以字符为单位进行存储,而是以二进制数据块为单位进行存储。在 C++ 中,可以使用二进制方式进行文件读写,实现对二进制文件的读写操作。
二进制文件的读写与文本文件的读写不同,主要体现在以下两个方面:
- 打开文件时需要使用二进制方式进行打开,即指定文件打开方式为 std::ios::binary。
- 读写操作时需要以二进制数据块为单位进行读写,而不是以字符为单位进行读写。
以下是一个使用二进制方式读写二进制文件的示例:
#include <iostream> #include <fstream> struct Person { char name[20]; int age; double height; }; int main() { std::ofstream outfile("example.bin", std::ios::binary); if (!outfile) { std::cerr << "Failed to open file for writing" << std::endl; return -1; } Person person = {"Alice", 28, 1.65}; outfile.write((const char*)&person, sizeof(person)); outfile.close(); std::ifstream infile("example.bin", std::ios::binary); if (!infile) { std::cerr << "Failed to open file for reading" << std::endl; return -1; } Person read_person; infile.read((char*)&read_person, sizeof(read_person)); std::cout << "Name: " << read_person.name << std::endl; std::cout << "Age: " << read_person.age << std::endl; std::cout << "Height: " << read_person.height << std::endl; infile.close(); return 0; }
在上述代码中,首先使用 ofstream 类以二进制方式打开文件,并将一个 Person 结构体对象写入文件中。然后使用 ifstream 类以二进制方式打开文件,并读取文件中的二进制数据块,将其解析为一个 Person 结构体对象,并输出其成员变量的值。
需要注意的是,在进行二进制文件读写操作时,需要注意文件读写指针的位置,以保证二进制数据块的正确读写。另外,在进行文件读写操作时,应当确保写入和读取的二进制数据块的大小相同,否则可能会导致数据的丢失或者文件内容的不一致。
文件拷贝程序mycopy 示例
/*用法示例: mycopy src.dat dest.dat 即将 src.dat 拷贝到 dest.dat 如果 dest.dat 原来就有,则原来的文件会被覆 盖 */ #include <iostream> #include <fstream> using namespace std; int main(int argc, char * argv[]) { if( argc != 3 ) { cout << "File name missing!" << endl; return 0; } ifstream inFile(argv[1],ios::binary|ios::in); //打开文件用 于读 if( ! inFile ) { cout << "Source file open error." << endl; return 0; } ofstream outFile(argv[2],ios::binary|ios::out); //打开文 件用于写 if( !outFile) { cout << "New file open error." << endl; inFile.close(); //打开的文件一定要关闭 return 0; } char c; while( inFile.get(c)) //每次读取一个字符 outFile.put(c); //每次写入一个字符 outFile.close(); inFile.close(); return 0; }
二进制文件和文本文件的区别
Linux,Unix下的换行符号:‘\n’ (ASCII码: 0x0a)
Windows 下的换行符号:‘\r\n’ (ASCII码: 0x0d0a) endl 就是 ‘\n’
Mac OS下的换行符号: ‘\r’ (ASCII码:0x0d)
导致 Linux, Mac OS 文本文件在Windows 记事本中打开时不换行二进制文件和文本文件的区别
Unix/Linux下打开文件,用不用 ios::binary 没区别
Windows下打开文件,如果不用 ios::binary,
则:读取文件时,所有的 ‘\r\n’会被当做一个字符’\n’处理,即少读了一个字
符’\r’。
写入文件时,写入单独的’\n’时,系统自动在前面加一个’\r’,即多写了一
个’\r
函数模板
交换两个整型变量的值的Swap函数: void Swap(int & x,int & y) { int tmp = x; x = y; y = tmp; } 交换两个double型变量的值的Swap函数: void Swap(double & x,double & y) { double tmp = x; x = y; y = tmp; }
用函数模板解决:
用函数模板解决: template <class 类型参数1,class 类型参数2,……> 返回值类型 模板名 (形参表) { 函数体 }; template <class T> void Swap(T & x,T & y) { T tmp = x; x = y; y = tmp; } 函数模板 int main() { int n = 1,m = 2; Swap(n,m); //编译器自动生成 void Swap(int & ,int & )函数 double f = 1.2,g = 2.3; Swap(f,g); //编译器自动生成 void Swap(double & ,double & )函数 return 0; } void Swap(double & x,double & y) { double tmp = x; x = y; y = tm
在C++中,函数模板是一种通用的函数定义,可以应用于不同的数据类型。它允许编写一次代码以适应多种不同的数据类型,实现代码的复用和泛化。
函数模板使用关键字 “template” 开始,并且后面跟着模板参数列表。模板参数列表可以包含一个或多个类型参数(如T、U等)或非类型参数(如整数常量)。例如:
template <typename T> T max(T a, T b) { return (a > b) ? a : b; }
上述代码中的函数模板 max
接受两个相同类型的参数,并返回较大的值。类型参数 T
可以是任何数据类型,比如整数、浮点数、字符等。
在实际调用函数模板时,编译器根据参数的类型将模板进行实例化,生成对应类型的函数。例如:
int result1 = max<int>(3, 5); // 实例化为 max<int>, 返回 5 double result2 = max<double>(2.7, 1.5); // 实例化为 max<double>, 返回 2.7 char result3 = max<char>('a', 'b'); // 实例化为 max<char>, 返回 'b'
在上面的例子中,通过 <类型>
的形式来指定实例化的具体类型,这样编译器就能够根据传入的类型生成对应的函数。如果没有显式指定类型,编译器会根据参数的类型自动推导出实例化的类型。
函数模板还可以有多个类型参数,并且可以有默认参数值。此外,你还可以在函数模板外定义非模板函数,它们可以与函数模板进行重载。
函数模板是C++中一种强大的工具,利用它可以编写通用且具有复用性的代码,可以处理不同类型的数据。
当需要在函数模板中处理多个不同类型的参数时,可以使用多个类型参数。
例如,下面是一个函数模板 swap
,用于交换两个值:
template <typename T> void swap(T& a, T& b) { T temp = a; a = b; b = temp; }
上述代码中的 swap
函数模板接受两个相同类型的引用参数,并交换它们的值。使用该函数模板时,编译器将根据实参的类型实例化对应的函数。
int x = 5, y = 10; swap(x, y); // 实例化为 swap<int>(x, y),交换 x 和 y 的值 double a = 2.5, b = 3.7; swap(a, b); // 实例化为 swap<double>(a, b),交换 a 和 b 的值
除了类型参数,函数模板还可以包含非类型参数。非类型参数可以是整数、枚举、指针或引用类型,但不能是浮点数、类类型或 void 类型。
下面是一个示例,演示如何在函数模板中使用非类型参数来指定数组的大小:
template <typename T, int size> void printArray(const T (&arr)[size]) { for (int i = 0; i < size; ++i) { cout << arr[i] << " "; } cout << endl;
上述代码中的 printArray
函数模板接受一个固定大小的数组,并打印每个元素。通过将数组大小作为非类型参数传递给函数模板,可以在编译时知道数组的大小。
int intArray[] = {1, 2, 3, 4, 5}; printArray(intArray); // 实例化为 printArray<int, 5>(intArray) double doubleArray[] = {1.5, 2.7, 3.9}; printArray(doubleArray); // 实例化为 printArray<double, 3>(doubleArray)
这样,函数模板就可以根据不同的数组大小生成对应的函数。
需要注意的是,在函数模板的定义和声明中,通常将模板参数放在尖括号 < >
中,并使用关键字 typename
或 class
来声明类型参数。然而,你也可以使用非类型参数来调整模板的行为。
同时,函数模板还可以具有默认模板参数,以便更灵活地使用。默认模板参数允许指定某个或某些参数的默认值,使得在函数调用时可以省略掉这些参数。
C++中的函数模板是一种强大的工具,可以处理多个不同类型的参数,其中可以包含类型参数和非类型参数。通过使用函数模板,可以实现通用、可复用的代码,并根据实参的类型和值来自动生成对应的函数。
函数模板和函数的次序
在有多个函数和函数模板名字相同的情况下,编译器如下处理一条函数调用语句
- 先找参数完全匹配的普通函数(非由模板实例化而得的函数)。
- 再找参数完全匹配的模板函数。
- 再找实参数经过自动类型转换后能够匹配的普通函数。
- 上面的都找不到,则报错。
类模板
类模板 – 问题的提出
• 为了多快好省地定义出一批相似的类,可以定义类模板,然后由类模
板生成不同的类
• 数组是一种常见的数据类型,元素可以是:
– 整数
– 学生
– 字符串
– ……
• 考虑一个可变长数组类,需要提供的基本操作
– len():查看数组的长度
– getElement(int index):获取其中的一个元素
– setElement(int index):对其中的一个元素进行赋值
– ……
### 类模板的定义
template <typename 类型参数1,typename 类型参数2,……> //类型参数表 class 类模板名 { 成员函数和成员变量 }; 类模板里成员函数的写法: template <class 类型参数1,class 类型参数2,……> //类型参数表 返回值类型 类模板名<类型参数名列表>::成员函数名(参数表) { …… } 用类模板定义对象的写法: 类模板名 <真实类型参数表> 对象名(构造函数实参表); 类模板示例: Pair类模板 template <class T1,class T2> class Pair { public: T1 key; //关键字 T2 value; //值 Pair(T1 k,T2 v):key(k),value(v) { }; bool operator < ( const Pair<T1,T2> & p) const; }; template<class T1,class T2> bool Pair<T1,T2>::operator < ( const Pair<T1,T2> & p) const //Pair的成员函数 operator < { return key < p.key; }
类模板示例:Pair类模板
int main() { Pair<string,int> student("Tom",19); //实例化出一个类 Pair<string,int> cout << student.key << " " << student.value; return 0; } 输出: Tom 19
类模板(Class Template)是C++中另一种通用编程的工具,它允许定义一种通用的类,可以用于不同的数据类型。
类模板使用关键字 template
开始,并在尖括号 < >
中包含一个或多个类型参数。类型参数可以在类定义的内部作为类型的占位符使用。例如:
template <typename T> class MyStack { private: T* elements; int top; int capacity; public: MyStack(int size) { elements = new T[size]; capacity = size; top = -1; } // 其他成员函数的实现省略... };
上述代码中的 MyStack
类模板定义了一个栈的数据结构,其中的元素类型 T
是一个占位符,在实际使用时会根据传入的类型进行实例化。
在实际使用类模板时,需要根据实际需求显式实例化特定类型的类。例如:
MyStack<int> intStack(10); // 实例化为 MyStack<int> MyStack<double> doubleStack(5); // 实例化为 MyStack<double>
在上述代码中,分别创建了一个 int
类型和一个 double
类型的 MyStack
对象。每个对象都是根据相应的类型实例化的类。
类模板还可以有多个类型参数,并且可以包含非类型参数,与函数模板类似。可以通过提供特定类型的实参来显式指定实例化的类型,也可以让编译器自动推导类型。
类模板还可以有成员函数、成员变量、构造函数和析构函数等,可以根据具体需求进行实现。在定义类模板的成员函数时,可以像普通类一样使用 T
来引用模板参数所表示的类型。
template <typename T> class MyStack { // ... void push(T element) { if (top + 1 >= capacity) { // 扩展容量代码... } elements[++top] = element; } T pop() { if (top < 0) { // 异常处理代码... } return elements[top--]; } // ... };
总结一下,类模板是C++中用于定义通用类的工具,允许创建可以适应不同数据类型的类。类模板使用关键字 template
和类型参数来定义,并通过显式实例化或类型推导来生成特定类型的类。类模板还可以包含成员函数、成员变量等,用于处理特定类型的数据。
用类模板定义对象
编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类,叫模板类。
同一个类模板的两个模板类是不兼容的
同一个类模板的两个模板类是不兼容的
是的,对于同一个类模板,不同的模板实例(即不同的模板参数)会生成不同的类型,这些类型在编译时是完全独立和不兼容的。
例如,考虑下面的类模板 MyStack
:
template <typename T> class MyStack { // ... };
使用 MyStack<int>
实例化的对象和使用 MyStack<double>
实例化的对象是完全不同的类型,它们之间是不兼容的。
MyStack<int> intStack; MyStack<double> doubleStack; intStack.push(5); doubleStack.push(3.14); int x = intStack.pop(); // 类型为 int double y = doubleStack.pop(); // 类型为 double
在上述代码中,intStack
和 doubleStack
是两个完全不同的对象,它们的行为和类型都是根据实例化时的模板参数来确定的。
由于不同的模板实例生成的类型是不兼容的,因此不能将 MyStack<int>
的对象赋值给 MyStack<double>
的对象,也不能将它们混合使用。
MyStack<int> intStack; MyStack<double> doubleStack; // 以下代码是不允许的,会导致类型错误: doubleStack = intStack; // 错误:不兼容的类型 double x = intStack.pop(); // 错误:类型不匹配 intStack.push(3.14); // 错误:类型不匹配
因此,对于同一个类模板生成的不同模板实例,它们是不兼容的,并且在使用时需要注意保持类型一致。
函数模版作为类模板成员
函数模板可以作为类模板的成员函数。类模板中的成员函数也可以是函数模板,允许在不同的实例化类型上进行通用操作。
下面是一个示例,演示了如何在类模板中定义函数模板作为成员函数:
template <typename T> class MyVector { private: T* elements; int size; public: MyVector(int s) : size(s) { elements = new T[size]; } template <typename U> void setValue(int index, U value) { if (index >= 0 && index < size) { elements[index] = static_cast<T>(value); } } // 其他成员函数的实现... };
在上述代码中,MyVector
是一个类模板,其中定义了一个名为 setValue
的成员函数模板。此函数模板接受两个参数,一个是 index
表示要设置值的索引,另一个是 value
表示要设置的值。该函数模板可以适用于不同的数据类型 T
和 U
。
使用示例:
MyVector<int> myIntVector(5); myIntVector.setValue(0, 10); // 设置索引0处的值为10 MyVector<double> myDoubleVector(3); myDoubleVector.setValue(1, 3.14); // 设置索引1处的值为3.14
在上述示例中,我们分别创建了一个 MyVector<int>
和一个 MyVector<double>
对象,并使用 setValue
函数模板设置了不同类型的值。
通过在类模板中定义函数模板,可以实现对不同类型的数据进行通用操作,增加了代码的灵活性和复用性。
类模板与派生
• 类模板从类模板派生
• 类模板从模板类派生
• 类模板从普通类派生
• 普通类从模板类派生
类模板可以作为基类用于派生其他类。通过派生,可以在派生类中使用基类的模板参数,并添加额外的成员变量和成员函数。
下面是一个示例,演示了如何使用类模板作为基类进行派生:
template <typename T> class MyBaseTemplate { protected: T data; public: MyBaseTemplate(const T& value) : data(value) {} void printData() const { std::cout << "Data: " << data << std::endl; } }; template <typename T> class MyDerivedTemplate : public MyBaseTemplate<T> { private: int additionalData; public: MyDerivedTemplate(const T& value, int additional) : MyBaseTemplate<T>(value), additionalData(additional) {} void printAllData() const { MyBaseTemplate<T>::printData(); std::cout << "Additional Data: " << additionalData << std::endl; } };
在上述代码中,MyBaseTemplate
是一个类模板,它有一个模板参数 T
和一个成员变量 data
。派生类 MyDerivedTemplate
继承自 MyBaseTemplate<T>
,并添加了一个额外的成员变量 additionalData
。
派生类中的构造函数使用基类的构造函数进行初始化,并将额外的参数传递给派生类的成员变量。
派生类还可以调用基类的成员函数,如示例中的 printData()
函数。使用作用域解析运算符 ::
可以访问基类的成员函数。
使用示例:
MyDerivedTemplate<int> myDerived(10, 20); myDerived.printAllData();
在上述示例中,我们创建了一个 MyDerivedTemplate<int>
对象,并将值 10
和 20
分别传递给基类和派生类的构造函数。然后,调用派生类的 printAllData()
函数,它会分别打印基类的数据和派生类的额外数据。
通过派生,我们可以在派生类中扩展和特化基类模板的功能,实现更灵活和具体化的代码。
类模板从类模板派生
类模板可以从另一个类模板派生,这样可以在派生类中使用基类的模板参数,并添加额外的模板参数和成员函数。
下面是一个示例,演示了如何从类模板派生另一个类模板:
template <typename T> class MyBaseTemplate { protected: T data; public: MyBaseTemplate(const T& value) : data(value) {} void printData() const { std::cout << "Data: " << data << std::endl; } }; template <typename T, typename U> class MyDerivedTemplate : public MyBaseTemplate<T> { private: U additionalData; public: MyDerivedTemplate(const T& value, const U& additional) : MyBaseTemplate<T>(value), additionalData(additional) {} void printAllData() const { MyBaseTemplate<T>::printData(); std::cout << "Additional Data: " << additionalData << std::endl; } };
在上述代码中,MyBaseTemplate
是一个类模板,它有一个模板参数 T
和一个成员变量 data
。派生类 MyDerivedTemplate
是一个带有两个模板参数 T
和 U
的类模板,它从 MyBaseTemplate<T>
派生而来,并添加了一个额外的模板参数 U
和成员变量 additionalData
。
派生类中的构造函数使用基类的构造函数进行初始化,并将额外的参数传递给派生类的成员变量。
派生类还可以调用基类的成员函数,使用作用域解析运算符 ::
可以访问基类的成员函数。
使用示例:
MyDerivedTemplate<int, double> myDerived(10, 3.14); myDerived.printAllData();
在上述示例中,我们创建了一个 MyDerivedTemplate<int, double>
对象,并将值 10
和 3.14
分别传递给基类和派生类的构造函数。然后,调用派生类的 printAllData()
函数,它会分别打印基类的数据和派生类的额外数据。
通过从类模板派生另一个类模板,可以实现更加灵活和通用的代码结构,同时具备模板参数的扩展能力。
普通类从模板类派生
普通类也可以从模板类派生,这样可以在派生类中使用模板类的具体化版本。派生类不需要显式地指定模板参数,因为已经在模板类中进行了定义。
下面是一个示例,演示了如何从模板类派生普通类:
template <typename T> class MyTemplateClass { protected: T data; public: MyTemplateClass(const T& value) : data(value) {} void printData() const { std::cout << "Data: " << data << std::endl; } }; class MyDerivedClass : public MyTemplateClass<int> { private: int additionalData; public: MyDerivedClass(const int& value, int additional) : MyTemplateClass<int>(value), additionalData(additional) {} void printAllData() const { MyTemplateClass<int>::printData(); std::cout << "Additional Data: " << additionalData << std::endl; } };
在上述代码中,MyTemplateClass
是一个模板类,它有一个模板参数 T
和一个成员变量 data
。派生类 MyDerivedClass
从 MyTemplateClass<int>
派生而来,并添加了一个额外的成员变量 additionalData
。
派生类的构造函数使用基类的具体化版本 MyTemplateClass<int>
进行初始化,并将额外的参数传递给派生类的成员变量。
派生类可以调用基类的成员函数,使用作用域解析运算符 ::
可以访问基类的成员函数。
使用示例:
MyDerivedClass myDerived(10, 20); myDerived.printAllData();
在上述示例中,我们创建了一个 MyDerivedClass
对象,并将值 10
和 20
分别传递给基类和派生类的构造函数。然后,调用派生类的 printAllData()
函数,它会分别打印基类的数据和派生类的额外数据。
通过从模板类派生普通类,可以使用特定的模板参数类型,而不必在派生类中指定额外的模板参数。这样可以更方便地使用模板类的功能,并提供更具体化的代码实现。
类模板与友员函数
• 函数、类、类的成员函数作为类模板的友元
• 函数模板作为类模板的友元
• 函数模板作为类的友元
• 类模板作为类模板的友元
类模板可以定义友元函数,这样友元函数可以访问类模板的私有成员和保护成员。友元函数可以在类定义内或外定义。
以下是一个示例,演示了如何在类模板中定义和使用友元函数:
template <typename T> class MyTemplateClass { private: T data; public: MyTemplateClass(const T& value) : data(value) {} template <typename U> friend void printData(const MyTemplateClass<U>& obj); }; template <typename U> void printData(const MyTemplateClass<U>& obj) { std::cout << "Data: " << obj.data << std::endl; }
在上述代码中,MyTemplateClass
是一个类模板,它有一个模板参数 T
和一个私有成员变量 data
。类模板中定义了一个友元函数 printData
,该函数可以访问 MyTemplateClass
的私有成员变量 data
。
用户可以在类模板定义内部或外部定义友元函数。在上述示例中,友元函数 printData
的定义位于类模板定义外部,但在定义友元函数时需要使用类模板的具体化版本 MyTemplateClass<U>
。
使用示例:
MyTemplateClass<int> obj(10); printData(obj);
在上述示例中,我们创建了一个 MyTemplateClass<int>
对象,并将值 10
传递给构造函数。然后,我们调用友元函数 printData
,它会打印类模板对象的私有成员变量 data
。
通过定义友元函数,类模板可以在需要访问私有或保护成员时提供额外的灵活性和扩展性。这使得友元函数可以直接操作类模板对象的内部数据,而无需通过公有接口。
在C++中,函数、类、类的成员函数和函数模板都可以作为类模板的友元。下面分别介绍这些情况:
- 函数作为类模板的友元:
template <typename T> class MyTemplateClass { // 声明函数为友元 friend void myFriendFunction<T>(const MyTemplateClass<T>& obj); }; template <typename T> void myFriendFunction(const MyTemplateClass<T>& obj) { // 可以访问MyTemplateClass的私有成员和保护成员 }
- 类作为类模板的友元:
template <typename T> class MyFriendClass { // ... }; template <typename T> class MyTemplateClass { // 声明类为友元 friend class MyFriendClass<T>; };
- 类的成员函数作为类模板的友元:
template <typename T> class MyTemplateClass { private: T data; // 声明类的成员函数为友元 friend void MyTemplateClass<T>::myFriendMemberFunction(); void myFriendMemberFunction() { // 可以访问MyTemplateClass的私有成员和保护成员 } };
- 函数模板作为类模板的友元:
template <typename T> class MyTemplateClass { // 声明函数模板为友元 template <typename U> friend void myFriendFunctionTemplate(const MyTemplateClass<U>& obj); }; template <typename U> void myFriendFunctionTemplate(const MyTemplateClass<U>& obj) { // 可以访问MyTemplateClass的私有成员和保护成员 }
- 类模板作为类模板的友元:
template <typename T> class MyFriendClass { // ... }; template <typename T> class MyTemplateClass { // 声明类模板为友元 template <typename U> friend class MyFriendClass<U>; };
以上示例为各种不同情况下如何声明和使用类模板的友元。友元关系允许其他函数、类或成员函数访问类模板中的私有成员和保护成员,从而提供更大的灵活性和扩展性。请根据实际需求选择适合的友元类型。
类模板与静态成员变量
类模板与static成员
• 类模板中可以定义静态成员,那么从该类模板实例化得到的所有类, 都包含同样的静态成员。 #include <iostream> using namespace std; template <class T> class A { private: static int count; public: A() { count ++; } ~A() { count -- ; }; A( A & ) { count ++ ; } static void PrintCount() { cout << count << endl; } }; 类模板与static成员 template<> int A<int>::count = 0; template<> int A<double>::count = 0; int main() { A<int> ia; A<double> da; ia.PrintCount(); da.PrintCount(); return 0; }
输出:
1
1
类模板与静态成员变量
类模板和静态成员变量可以结合使用。可以在类模板中声明和定义静态成员变量,并且所有实例化的类都共享同一个静态成员变量。以下是示例代码:
template <typename T> class MyTemplateClass { public: static int count; // 声明静态成员变量 MyTemplateClass() { count++; // 在构造函数中对静态成员变量进行操作 } }; template <typename T> int MyTemplateClass<T>::count = 0; // 静态成员变量的定义和初始化 int main() { MyTemplateClass<int> obj1; MyTemplateClass<int> obj2; MyTemplateClass<double> obj3; std::cout << "Count for int: " << MyTemplateClass<int>::count << std::endl; // 输出2 std::cout << "Count for double: " << MyTemplateClass<double>::count << std::endl; // 输出1 return 0; }
在上述示例中,MyTemplateClass
是一个类模板,其中声明了一个静态成员变量 count
。在类模板外部,我们需要对静态成员变量进行定义和初始化,使用类似于普通类的静态成员变量的语法。
在 main()
函数中,我们创建了几个类模板的实例。每当创建一个实例时,构造函数会自动递增静态成员变量 count
。因为静态成员变量是被所有实例共享的,所以每个实例的构造都会影响到所有实例。
最后,我们通过类名加作用域解析运算符 ::
来访问不同类型的静态成员变量,并将其输出到控制台。
总结来说,类模板可以具有静态成员变量,并且所有实例化的类都共享同一个静态成员变量。这在跟踪和计数类模板对象的数量时非常有用。
标准模版库
https://blog.csdn.net/shaozheng0503/article/details/129101932?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168802585416800211563089%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168802585416800211563089&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-129101932-null-null.268v1koosearch&utm_term=stl&spm=1018.2226.3001.4450
标准模板库(Standard Template Library,STL)是C++的一个重要组成部分,提供了一套丰富的数据结构和算法容器。STL中的容器、算法和迭代器是三个主要的组件。
以下是STL中常见的几个组件:
- 容器(Containers):STL提供了各种容器,包括向量(vector)、链表(list)、双向链表(deque)、集合(set)、映射(map)等。这些容器类似于数据结构,用于存储和管理数据。
- 算法(Algorithms):STL提供了大量的算法,包括排序、搜索、合并、查找等操作。这些算法可以应用于各种不同的容器,使得对容器进行操作变得非常简单。
- 迭代器(Iterators):迭代器是用于遍历容器中元素的一种通用方式。STL中的迭代器提供了一种统一的接口,使得可以以相同的方式访问不同类型的容器。
- 函数对象(Function Objects):函数对象是可调用对象(如函数指针或重载了函数调用运算符的类对象),用于在算法中定义自定义的操作。
- 分配器(Allocators):分配器用于管理内存的分配和释放,可以在STL的容器和算法中使用不同的分配器。
STL的设计目标是提供高效、灵活、通用的数据结构和算法,使得C++开发者可以更加便捷地进行编程。使用STL可以大大减少开发时间和代码复杂性,并且提供了可移植性和重用性。在C++开发中,使用STL可以极大地提高代码质量和开发效率。
泛型程序设计
C++ 语言的核心优势之一就是便于软件的重用
C++中有两个方面体现重用:
1.面向对象的思想:继承和多态,标准类库
2.泛型程序设计(generic programming) 的思想: 模板机制,以及标准模板库 STL
简单地说就是使用模板的程序设计法。
将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成模板,以后则不论数据结构里放的是什么对象,算法针对什么样的对象,则都不必重新实现数据结构,重新编写算法。
标准模板库 (Standard Template Library) 就是一些常用数据结构和算法的模板的集合。有了STL,不必再写大多的标准数据结构和算法,并且可获得非常高的性能。
STL中的基本的概念
容器:可容纳各种数据类型的通用数据结构,是类模板
迭代器:可用于依次存取容器中元素,类似于指针
算法:用来操作容器中的元素的函数模板
sort()来对一个vector中的数据进行排序
find()来搜索一个list中的对象
算法本身与他们操作的数据的类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用。
int array[100]; 该数组就是容器,而 int * 类型的指针变量就可以作为迭代器,sort算 法可以作用于该容器上,对其进行排序: sort(array,array+70); //将前70个元素排序 迭代器
容器概述
没错,您提到的是STL中的一些常见容器和容器适配器。让我详细介绍一下它们:
- 顺序容器:
- 向量(vector):向量是一个动态数组,能够在其末尾高效地添加和删除元素。它还支持随机访问,即可以通过索引直接访问元素。
- 双端队列(deque):双端队列与向量类似,但也允许在头部进行高效插入和删除操作。
- 链表(list):链表是一种双向链表,可以在任意位置进行高效的插入和删除操作,但无法通过索引直接访问元素。
- 关联容器:
- 集合(set):集合是一个有序且不含重复元素的容器。它支持高效的搜索、插入和删除操作。
- 多重集合(multiset):多重集合与集合类似,但允许容器中存在重复的元素。
- 映射(map):映射是一种键-值对的容器,每个元素都有一个唯一的键。它支持按照键进行高效的搜索、插入和删除操作。
- 多重映射(multimap):多重映射与映射类似,但允许容器中存在重复的键。
- 容器适配器:
- 栈(stack):栈是一种后进先出(LIFO)的容器适配器。它基于双端队列或列表实现,只允许在顶部进行插入和删除操作。
- 队列(queue):队列是一种先进先出(FIFO)的容器适配器。它基于双端队列或列表实现,允许在一端进行插入,在另一端进行删除。
- 优先队列(priority_queue):优先队列是一种基于堆的容器适配器,它保证队列中具有最高优先级的元素始终位于队列前端。
这些容器和容器适配器提供了不同的数据组织方式和操作特性,可以根据需要选择最合适的容器来存储和管理数据。每个容器都有其独特的优势和适用场景,了解它们的特点和使用方法将对编程非常有帮助。
对象被插入容器中时,被插入的是对象的一个复制品。许多算法,比如排序,查找,要求对容器中的元素进行比较,有的容器本身就是排序的,所以,放入容器的对象所属的类,往往还应该重载 == 和 < 运算符。
迭代器
用于指向顺序容器和关联容器中的元素
迭代器用法和指针类似
有const 和非 const两种
通过迭代器可以读取它指向的元素
通过非const迭代器还能修改其指向的元素
迭代器是STL中的一个重要概念,它用于遍历容器中的元素并访问它们。通过迭代器,我们可以以一种统一的方式对不同类型的容器进行操作,而不需要关心具体容器的实现细节。
STL中定义了多种类型的迭代器,每种迭代器具有不同的特性和功能。以下是STL中的常见迭代器分类:
- 输入迭代器(Input Iterator):输入迭代器用于在容器中从前向后遍历元素,并支持读取元素的值。输入迭代器只能单向移动,不支持修改容器中的元素。
- 输出迭代器(Output Iterator):输出迭代器用于在容器中从前向后遍历元素,并支持修改元素的值。输出迭代器只能单向移动,不支持读取元素的值。
- 正向迭代器(Forward Iterator):正向迭代器可以像输入迭代器和输出迭代器一样进行单向遍历和修改。与输入迭代器和输出迭代器不同的是,正向迭代器支持多次遍历,即可以对同一个容器进行多次迭代。
- 双向迭代器(Bidirectional Iterator):双向迭代器比正向迭代器更强大,它支持从前向后和从后向前遍历容器中的元素。双向迭代器可以进行递增和递减操作。
- 随机访问迭代器(Random Access Iterator):随机访问迭代器是最强大的迭代器类型,它具有所有其他迭代器的功能,并且支持随机访问容器中的元素。随机访问迭代器可以像指针一样进行算术运算,使得我们可以在常数时间内访问容器中的任意元素。
使用迭代器,我们可以通过以下方式遍历容器中的元素:
for (auto it = container.begin(); it != container.end(); ++it) { // 访问迭代器指向的元素 // *it 可以获取当前迭代器指向的元素 }
需要注意的是,不同类型的容器可能提供不同类型的迭代器。例如,向量和双端队列提供随机访问迭代器,而链表只提供双向迭代器。在编写代码时,我们需要根据具体容器的特性选择合适的迭代器类型来进行操作。
迭代器是STL的核心概念之一,掌握了迭代器的使用方法,可以更加灵活和高效地操作容器中的元素。
当然,继续回答您关于迭代器的问题。
在STL中,除了常见的迭代器类型外,还有一些其他类型的迭代器和相关概念:
- 反向迭代器(Reverse Iterator):反向迭代器是一种特殊的迭代器,它可以逆序遍历容器中的元素。通过
rbegin()
和rend()
成员函数可以获取反向迭代器的起始和结束位置。
for (auto rit = container.rbegin(); rit != container.rend(); ++rit) { // 访问反向迭代器指向的元素 }
- 常量迭代器(Const Iterator):常量迭代器用于对容器中的元素进行只读访问,不允许修改元素的值。通过
cbegin()
和cend()
成员函数可以获取常量迭代器的起始和结束位置。
for (auto cit = container.cbegin(); cit != container.cend(); ++cit) { // 访问常量迭代器指向的元素,只允许进行读取操作 }
- 插入迭代器(Insert Iterator):插入迭代器是一种特殊的迭代器,它可以在容器中插入新的元素。插入迭代器是通过
std::inserter()
函数创建的。
std::vector<int> vec; std::fill_n(std::inserter(vec, vec.begin()), 5, 0); // 在vec的开始位置插入5个0
- 流迭代器(Stream Iterator):流迭代器用于将容器中的元素通过输入输出流进行读写。通过
std::istream_iterator
和std::ostream_iterator
可以创建输入和输出流迭代器。
// 从标准输入流中读取整数,并存储到容器中 std::vector<int> vec(std::istream_iterator<int>(std::cin), std::istream_iterator<int>()); // 将容器中的元素通过空格分隔输出到标准输出流 std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));
这些特殊类型的迭代器扩展了STL对容器的操作能力,使得我们可以更灵活地使用迭代器进行元素的遍历、修改和插入等操作。根据实际需求,选择合适的迭代器类型能够提高代码的可读性和效率。
迭代器是用于指向顺序容器(如向量、列表等)和关联容器(如映射、集合等)中的元素的对象。它的用法与指针非常相似,可以通过迭代器访问和操作容器中的元素。
迭代器分为const迭代器和非const迭代器两种类型。const迭代器只能读取其指向的元素,而非const迭代器除了读取,还可以修改其指向的元素。
使用迭代器可以通过以下方式读取和修改元素:
- 读取元素:通过解引用操作符
*
来获取迭代器指向的元素的值。例如*it
获取迭代器it
指向的元素。 - 修改元素:通过解引用操作符
*
获取元素的引用,然后对引用进行修改。例如*it = value
将迭代器it
指向的元素设置为value
。
下面是一个使用迭代器进行读取和修改的示例代码:
#include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; // 使用非const迭代器读取和修改元素 for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) { // 读取元素的值 int value = *it; std::cout << value << " "; // 修改元素的值 *it = value * 2; } std::cout << std::endl; // 输出修改后的元素 for (const auto& num : vec) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出:
1 2 3 4 5 2 4 6 8 10
通过迭代器,我们可以灵活地对容器中的元素进行读取和修改,使得我们能够更方便地操作容器中的数据。