在C++中,friend
是一个关键字,用于声明友元关系。友元关系允许一个非成员函数或非成员类访问另一个类的私有成员。通过声明友元,可以提供对类的封装性的例外,使得其他类或函数可以访问类的私有成员。
友元关系可以在类的定义中通过在函数或类前加上friend
关键字来声明。有以下几种情况下可以使用友元关系:
友元函数:可以将非成员函数声明为友元函数,以便该函数可以访问类的私有成员。
class MyClass {
private:
int x;
public:
friend void friendFunction(); // 声明友元函数
void setX(int value) {
x = value;
}
};
void friendFunction() {
MyClass obj;
obj.x = 10; // 可以访问私有成员x
}
友元类:可以将一个类声明为另一个类的友元类,以便该友元类可以访问另一个类的私有成员。
class MyClass {
private:
int x;
public:
friend class FriendClass; // 声明友元类
void setX(int value) {
x = value;
}
};
class FriendClass {
public:
void accessPrivateMember() {
MyClass obj;
obj.x = 10; // 可以访问MyClass的私有成员x
}
};
需要注意的是,友元关系破坏了类的封装性,因此应谨慎使用。友元关系的声明通常出现在类的定义中,但不是类的成员。友元关系是单向的,声明类A为类B的友元并不意味着类B自动成为类A的友元。
总结起来,friend
关键字用于声明友元关系,在类的定义中可以将非成员函数声明为友元函数,或者将一个类声明为另一个类的友元类。友元关系允许其他类或函数访问类的私有成员,但同时也破坏了类的封装性,因此应谨慎使用。
1.友元函数定义在类外,不是类中的函数。需要访问类中的数据,参数通常是对象的引用,来访问对象中成员
2.因为不是类中的函数,只是在类中进行了friend说明。不受类中的权限修饰符的限制,可防止类中任意位置
#include
using namespace std;
class Test{
private:
int value;
public:
Test(int v){
value=v;
}
friend void show(Test& t);
void print(){
cout<<value<<endl;
}
};
void show(Test& t){
cout<<t.value<<endl;
t.value++;
}
int main()
{
Test t(10);
show(t); //10
t.print(); //11
}
如果类B时类A的友元类,那么类B就可以访问A中的所有成员,需要在A中进行说明
#include
using namespace std;
class A{
private:
int value;
public:
A(int v){
value=v;
}
void print(){
cout<<value<<endl;
}
friend class B; //说明类B是类A的朋友 类B就可以访问类A的所有成员
};
class B{
public:
void test(A & a){
cout<<a.value<<endl;
}
};
int main()
{
A a(50);
B b;
b.test(a); //50
}
友元关系具有以下几个特点:
访问私有成员:友元关系允许一个非成员函数或非成员类访问另一个类的私有成员。通过声明友元,可以绕过类的访问限制,从而直接访问私有成员。
非继承关系:友元关系不依赖于类之间的继承关系。一个类可以将另一个类的非成员函数声明为友元函数,或将一个类声明为另一个类的友元类,即使它们之间没有继承关系。
单向性:友元关系是单向的。如果类A将类B声明为友元类,那么类B不自动成为类A的友元类。友元关系必须显式地声明在类的定义中。
透明性:友元关系是透明的,即在友元函数或友元类中可以直接访问类的私有成员,就像它们是自己的成员一样。这使得友元函数可以更方便地操作和使用类的私有成员。
破坏封装性:友元关系破坏了类的封装性,因为它允许外部函数或类直接访问类的私有成员。因此,应该谨慎使用友元关系,确保其使用符合设计和安全性的要求。
友元关系提供了一种在特定情况下访问私有成员的机制,但同时也引入了一定的风险。友元关系应该被谨慎使用,只在必要的情况下才将函数或类声明为友元,以确保类的封装性和安全性。
运算符重载(Operator Overloading)是C++中一种特性,允许用户重新定义已有的运算符的行为。通过运算符重载,可以为用户自定义的类类型创建适合自己需求的运算符操作。
C++中可以重载的运算符包括算术运算符(如+
、-
、*
、/
)、关系运算符(如==
、!=
、<
、>
)、赋值运算符(如=
、+=
、-=
)、下标运算符([]
)、函数调用运算符(()
)等等。
以下是一个示例,演示如何重载加法运算符(+
):
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
// 重载加法运算符
MyNumber operator+(const MyNumber& other) {
MyNumber result(value + other.value);
return result;
}
int getValue() {
return value;
}
};
int main() {
MyNumber num1(5);
MyNumber num2(10);
MyNumber sum = num1 + num2; // 调用重载的加法运算符
cout << "Sum: " << sum.getValue() << endl; // 输出:Sum: 15
return 0;
}
在上面的示例中,MyNumber
类重载了加法运算符+
,使得两个MyNumber
对象可以通过+
运算符相加。重载的加法运算符返回一个新的MyNumber
对象,其值是两个操作数的和。
通过运算符重载,可以根据类的特性和需求来定义运算符的行为,使得类对象之间的运算更直观和方便。需要注意的是,运算符重载应该符合运算符的本意,并遵循一些约定和规则,以确保代码的可读性和正确性。
除了重载已有的运算符,C++还允许创建自定义的运算符,称为重载运算符。然而,对于重载运算符的使用应该谨慎,并且要确保它们的行为符合直觉和常识。
总结起来,运算符重载是C++中一种特性,允许用户重新定义已有的运算符的行为,以适应自定义类类型的需求。通过重载运算符,可以使类对象之间的运算操作更直观和方便。但需要注意的是,运算符重载应该符合运算符的本意,并遵循约定和规则。
当我们想要在类外部实现运算符重载时,通常使用友元函数。为了重载加法运算符 +
,我们可以声明一个友元函数,并在函数体内定义运算符的行为。
以下是一个示例,展示如何在类外部通过友元函数重载加法运算符 +
:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() {
return value;
}
// 声明友元函数
friend MyNumber operator+(const MyNumber& num1, const MyNumber& num2);
};
// 定义友元函数,重载加法运算符
MyNumber operator+(const MyNumber& num1, const MyNumber& num2) {
MyNumber result(num1.value + num2.value);
return result;
}
int main() {
MyNumber num1(5);
MyNumber num2(10);
MyNumber sum = num1 + num2; // 调用重载的加法运算符
cout << "Sum: " << sum.getValue() << endl; // 输出:Sum: 15
return 0;
}
在上面的示例中,我们在 MyNumber
类中声明了一个友元函数 operator+
,它接受两个 MyNumber
对象作为参数,并返回一个新的 MyNumber
对象,其值是两个操作数的和。在类外部定义了这个友元函数的行为。
通过友元函数的重载,我们可以在类外部使用 +
运算符对 MyNumber
对象进行相加操作。在主函数中,我们创建了两个 MyNumber
对象 num1
和 num2
,并将它们相加得到结果 sum
。
需要注意的是,友元函数在声明时并不是类的成员函数,因此它没有隐式的访问权限,也没有 this
指针。因此,在友元函数中无法直接访问类的非静态成员变量和非静态成员函数,除非将其声明为该类的友元函数。
通过友元函数重载运算符时,可以更加灵活地定义运算符的行为,并且可以在类外部直接使用运算符进行操作。友元函数的重载可以提供类与类之间的自定义运算符语义。
总结起来,通过友元函数可以在类外部重载加法运算符 +
,使得两个对象可以直接使用 +
运算符进行相加操作。友元函数的重载提供了灵活的运算符定义方式,并且可以在类外部直接使用运算符进行操作。
在C++中,我们还可以通过成员函数重载来重载加法运算符 +
。通过在类中定义一个成员函数来重载运算符,该成员函数将使用调用运算符的对象作为左操作数。
以下是一个示例,展示如何通过成员函数重载加法运算符 +
:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() const {
return value;
}
// 成员函数重载加法运算符
MyNumber operator+(const MyNumber& other) const {
MyNumber result(value + other.value);
return result;
}
};
int main() {
MyNumber num1(5);
MyNumber num2(10);
MyNumber sum = num1 + num2; // 调用重载的加法运算符
cout << "Sum: " << sum.getValue() << endl; // 输出:Sum: 15
return 0;
}
在上面的示例中,我们在 MyNumber
类中定义了一个成员函数 operator+
,它接受一个 MyNumber
对象作为参数,并返回一个新的 MyNumber
对象,其值是调用对象的值与参数对象的值的和。
通过成员函数的重载,我们可以使用成员访问运算符 .
来调用重载的加法运算符。在主函数中,我们创建了两个 MyNumber
对象 num1
和 num2
,并使用 +
运算符对它们进行相加操作,得到结果 sum
。
需要注意的是,成员函数重载的加法运算符只有一个参数,即右操作数。左操作数将自动成为调用运算符的对象。因此,在成员函数内部,可以直接访问调用对象的成员变量和成员函数。
通过成员函数重载加法运算符,可以使类对象之间的运算更符合直觉,并且使用更加简洁。然而,成员函数重载只能改变右操作数的行为,无法改变左操作数的行为。
总结起来,通过成员函数重载可以在类中重载加法运算符 +
,使得对象可以使用成员访问运算符 .
直接调用运算符进行相加操作。成员函数重载可以使类对象之间的运算更加直观和简洁。
#include
using namespace std;
class Complex{
private:
double real; //实部
double image; //虚部
public:
Complex(double real,double image){
this->real=real;
this->image=image;
}
void show(){
cout<<"复数的实部是:"<<real<<" 虚部是:"<<image<<endl;
}
Complex operator +(const Complex & other){
double r=this->real+other.real;
double i=this->image+other.image;
Complex n(r,i);
return n;
}
Complex operator +(double d){ //对象类型+double
double r=this->real+d;
double i=this->image;
Complex n(r,i);
return n;
}
//double类型+对象类型的友元函数说明
friend Complex operator +(double d,const Complex & c);
};
Complex operator +(double d,const Complex & c){
double r=c.real+d;
double i=c.image;
Complex n(r,i);
return n;
}
int main()
{
Complex c1(2.2,3);
Complex c2(1.7,2.2);
Complex c3=c1+c2; //对象类型+对象类型
c3.show(); //复数的实部是:3.9 虚部是:5.2
Complex c4=c3+0.1; //对象类型+double
c4.show(); //复数的实部是:4 虚部是:5.2
Complex c5=1.5+c4; //double类型+对象类型 需要友元函数重载
c5.show(); //复数的实部是:5.5 虚部是:5.2
}
可以被重载的运算符:
算术运算符:+、-、、/、%、++、–
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不被重载的运算符:
成员运算符 .、指针运算符 *、三目运算符 ? :、sizeof、作用域 ::
当我们想要重载前置递增运算符 ++
和后置递增运算符 ++
时,我们可以在类中定义两个成员函数进行重载。前置递增运算符返回递增后的对象的引用,而后置递增运算符返回递增前的对象的副本。
以下是一个示例,展示如何通过成员函数重载前置递增运算符 ++
和后置递增运算符 ++
:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() const {
return value;
}
// 前置递增运算符重载
MyNumber& operator++() {
value++;
return *this;
}
// 后置递增运算符重载
MyNumber operator++(int) {
MyNumber temp(*this);
value++;
return temp;
}
};
int main() {
MyNumber num(5);
// 前置递增
++num;
cout << "After prefix increment: " << num.getValue() << endl; // 输出:After prefix increment: 6
// 后置递增
MyNumber oldNum = num++;
cout << "After postfix increment: " << num.getValue() << endl; // 输出:After postfix increment: 7
cout << "Old number: " << oldNum.getValue() << endl; // 输出:Old number: 6
return 0;
}
在上面的示例中,我们在 MyNumber
类中定义了两个成员函数 operator++
。第一个函数是前置递增运算符的重载,它将对象的值递增,并返回递增后的对象的引用。第二个函数是后置递增运算符的重载,它将对象的值递增,并返回递增前的对象的副本。
在主函数中,我们创建了一个 MyNumber
对象 num
,并使用前置递增运算符 ++
对其进行递增操作,以及使用后置递增运算符 ++
进行递增操作。我们还使用一个额外的对象 oldNum
来存储后置递增运算符返回的递增前的对象。
需要注意的是,前置递增运算符返回对象的引用,以便可以进行连续的递增操作,而后置递增运算符返回递增前的对象的副本。因此,在后置递增运算符的重载中,我们使用一个临时对象 temp
来存储递增前的对象,然后递增值并返回 temp
。
通过成员函数重载前置递增运算符和后置递增运算符,我们可以使用类对象自身的成员函数来实现递增操作,并根据需要返回递增前后的对象或引用。
当我们想要重载前置递增运算符 ++
和后置递增运算符 ++
时,我们可以使用友元函数在类外部进行重载。友元函数可以访问类的私有成员,因此可以直接修改对象的值。
以下是一个示例,展示如何通过友元函数重载前置递增运算符 ++
和后置递增运算符 ++
:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() const {
return value;
}
// 声明友元函数
friend MyNumber& operator++(MyNumber& num); // 前置递增运算符
friend MyNumber operator++(MyNumber& num, int); // 后置递增运算符
};
// 前置递增运算符重载
MyNumber& operator++(MyNumber& num) {
num.value++;
return num;
}
// 后置递增运算符重载
MyNumber operator++(MyNumber& num, int) {
MyNumber temp(num);
num.value++;
return temp;
}
int main() {
MyNumber num(5);
// 前置递增
++num;
cout << "After prefix increment: " << num.getValue() << endl; // 输出:After prefix increment: 6
// 后置递增
MyNumber oldNum = num++;
cout << "After postfix increment: " << num.getValue() << endl; // 输出:After postfix increment: 7
cout << "Old number: " << oldNum.getValue() << endl; // 输出:Old number: 6
return 0;
}
在上面的示例中,我们声明了两个友元函数 operator++
,分别用于重载前置递增运算符和后置递增运算符。在这两个函数中,我们直接访问了 MyNumber
类的私有成员 value
,并对其进行递增操作。
在主函数中,我们创建了一个 MyNumber
对象 num
,并使用前置递增运算符 ++
和后置递增运算符 ++
对其进行递增操作。前置递增运算符直接修改对象的值并返回递增后的引用,而后置递增运算符先创建一个临时对象来存储递增前的值,然后递增对象的值并返回临时对象的副本。
通过友元函数重载前置递增运算符和后置递增运算符,我们可以在类外部定义运算符的行为,同时可以直接访问类的私有成员。
赋值运算符 =
是用于将一个对象的值赋给另一个对象的运算符。在C++中,我们可以通过重载赋值运算符 =
来定义对象的赋值行为。
赋值运算符通常被定义为一个成员函数,并接受一个参数,即要赋值的对象。它的返回类型通常是引用,以支持连续赋值操作,并返回赋值后的对象的引用。
以下是一个示例,展示如何通过成员函数重载赋值运算符 =
:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() const {
return value;
}
// 赋值运算符重载
MyNumber& operator=(const MyNumber& other) {
if (this != &other) { // 避免自我赋值
value = other.value;
}
return *this;
}
};
int main() {
MyNumber num1(5);
MyNumber num2(10);
num1 = num2; // 调用重载的赋值运算符
cout << "Value of num1: " << num1.getValue() << endl; // 输出:Value of num1: 10
return 0;
}
在上面的示例中,我们在 MyNumber
类中定义了一个成员函数 operator=
,它接受一个 MyNumber
对象作为参数,并将其值赋给调用对象。在赋值运算符的实现中,我们首先检查对象的地址是否与参数对象的地址相同,以避免自我赋值。然后,我们将参数对象的值赋给调用对象的值,并返回调用对象的引用。
在主函数中,我们创建了两个 MyNumber
对象 num1
和 num2
,然后使用赋值运算符 =
将 num2
的值赋给 num1
。赋值运算符会将 num2
的值赋给 num1
,从而将 num1
的值更新为 10
。
通过成员函数重载赋值运算符,我们可以自定义对象的赋值行为,实现对象之间的值传递。重载赋值运算符可以使得对象的赋值操作更加灵活和符合预期。
需要注意的是,对于包含动态分配内存或资源管理的类,还需要特别注意在赋值运算符中处理自我赋值和资源释放的问题,以确保正确的赋值行为和避免资源泄漏。
类型转换运算符重载允许我们在类中定义自定义的类型转换行为。通过重载类型转换运算符,我们可以将一个对象从一个类类型转换为另一个类型。
类型转换运算符重载的语法如下:
operator type() {
// 转换逻辑
}
其中,type
是要转换到的目标类型。在重载类型转换运算符时,它必须是一个非静态成员函数,没有返回类型和参数列表。
以下是一个示例,展示如何通过类型转换运算符重载将类对象转换为其他类型:
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
int getValue() const {
return value;
}
// 类型转换运算符重载
operator int() const {
return value;
}
};
int main() {
MyNumber num(5);
int convertedValue = static_cast<int>(num); // 调用重载的类型转换运算符
cout << "Converted value: " << convertedValue << endl; // 输出:Converted value: 5
return 0;
}
在上面的示例中,我们在 MyNumber
类中定义了一个类型转换运算符 operator int()
。该运算符将 MyNumber
类对象转换为 int
类型。在类型转换运算符的实现中,我们简单地返回对象的值。
在主函数中,我们创建了一个 MyNumber
对象 num
,然后使用类型转换运算符将其转换为 int
类型,并将结果存储在 convertedValue
中。通过调用重载的类型转换运算符,num
对象被转换为 int
类型,并输出转换后的值。
需要注意的是,类型转换运算符的重载应该谨慎使用,以避免意外的类型转换导致不可预料的结果。在使用类型转换运算符时,应该确保转换是合理的,并且不会引起歧义或混淆。
std::string
是 C++ 标准库中提供的用于处理字符串的类。它是一个可变长度的字符序列,提供了许多有用的成员函数和操作符,用于方便地进行字符串的操作和处理。
要使用 std::string
类,首先需要包含
头文件:
#include
然后可以使用 std::string
类型来声明字符串变量,并进行字符串的初始化和操作。以下是一些常用的 std::string
类的操作示例:
#include
#include
int main() {
std::string str1 = "Hello"; // 初始化字符串
std::cout << "str1: " << str1 << std::endl; // 输出字符串
std::string str2 = "World";
str1 += " "; // 字符串拼接
str1 += str2;
std::cout << "Concatenated string: " << str1 << std::endl; // 输出拼接后的字符串
std::cout << "Length of str1: " << str1.length() << std::endl; // 获取字符串长度
std::string substring = str1.substr(6, 5); // 提取子串
std::cout << "Substring: " << substring << std::endl;
return 0;
}
在上面的示例中,我们使用 std::string
类型声明了 str1
和 str2
两个字符串变量。通过 =
运算符进行初始化,并使用 +=
运算符进行字符串的拼接操作。我们还使用了 length()
成员函数来获取字符串的长度,并使用 substr()
成员函数提取子串。
std::string
类还提供了许多其他有用的成员函数,例如比较字符串、查找子串、替换字符串、转换大小写等。
需要注意的是,std::string
类属于 C++ 的标准库,因此在使用时需要包含相应的头文件,并在代码中使用 std::
命名空间或显式指定 std::string
类的名称。
#include
using namespace std;
int main()
{
string s;
//判断是否位空 为空返回ture 也就是1
cout<<s.empty()<<endl; //bool类型。满足条件 返回true 1 否则返回false 0
string s1="hello"; //隐式调用构造函数
cout<<s1.empty()<<endl; //不为空 返回0
string s2("world"); //显示构造
string s3=s1; //拷贝构造
string s4(s2);
cout<<s3<<" "<<s4<<endl; //hello world
s3=s4; //赋值运算符重载
cout<<s3<<" "<<s4<<endl; //world world
//第一个参数:删除起始位置
//第二个参数:删除个数
s4.erase(0,2);
cout<<s4<<endl; //rld
//第一个参数:替换起始位置
//第二个参数:替换个数
//第三个参数:替换的新的内容
s4.replace(1,1,"***");
cout<<s4<<endl; //r***d
cout<<s3<<" "<<s4<<endl; //world r***d
swap(s3,s4); //交换
cout<<s3<<" "<<s4<<endl; //world r***d
//string类型之间的比较都是比的编码
bool b=(s3==s4);
cout<<b<<endl; //0
string s5="China";
string s6="Chinb";
bool b2=(s5>s6);
cout<<b2<<endl; //0
//追加
s6.append("###");
cout<<s6<<endl; //Chinb###
string s7=s5+s6;
cout<<s7<<endl; //ChinaChinb###
//插入
s7.insert(1,"****");
cout<<s7<<endl; //C****hinaChinb###
}
c++中使用模板可以实现”参数化多态“,可以让类或函数声明一种通用的类型,使函数或者类的编写与类型无关
模板有两种实现方式:
1.函数模板
2.类模板
函数模板是 C++ 中一种用于定义通用函数的机制,允许我们编写可以处理不同类型的数据的通用函数。函数模板使用参数化类型,也称为模板参数,来表示函数中的参数类型,从而在编译时生成特定类型的函数。
函数模板的语法如下:
template <typename T>
T functionName(T parameter) {
// 函数体
}
其中,template
关键字用于指示接下来的代码是一个模板,typename
或 class
关键字用于定义模板参数的名称(可以是任何合法的标识符),T
是模板参数的名称。
以下是一个示例,展示如何使用函数模板来实现一个通用的最大值函数:
#include
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int intMax = maximum(5, 10); // 调用模板函数,推导出 T 为 int
std::cout << "Maximum of 5 and 10: " << intMax << std::endl;
double doubleMax = maximum(3.14, 2.71); // 调用模板函数,推导出 T 为 double
std::cout << "Maximum of 3.14 and 2.71: " << doubleMax << std::endl;
return 0;
}
在上面的示例中,我们定义了一个函数模板 maximum
,它接受两个参数并返回较大的值。在 main
函数中,我们通过调用 maximum
函数来比较不同类型的数据。
通过函数模板,我们可以在不同的类型上重用相同的函数逻辑,从而提高代码的重用性和灵活性。编译器会根据函数调用的参数类型自动实例化模板,并生成相应的函数。
需要注意的是,函数模板可以有多个模板参数,并且可以使用模板参数进行类型推断、模板特化和默认模板参数等进一步扩展。函数模板也可以与函数重载结合使用,以提供更多的重载选择。
#include
using namespace std;
//int add(int a,int b){
// return a+b;
//}
//double add(double a,double b){
// return a+b;
//}
//string add(string a,string b){
// return a+b;
//}
template <class T>
T add(T a,T b){
return a+b;
}
int main()
{
int a=10,b=20;
cout<<add(a,b)<<endl;
double d1=3.5,d2=5.5;
cout<<add(d1,d2)<<endl; //9
string s1="hello";
string s2="world";
cout<<add(s1,s2)<<endl; //helloworld
}
mySwap() 参数传入任意两个类型变量, 可以进行交换
#include
using namespace std;
template <class T>
void mySwap(T& a,T& b){
T t =a;
a=b;
b=t;
}
int main()
{
int a=2,b=3;
mySwap(a,b);
cout<<a<<" "<<b<<endl;
string s1="hello";
string s2="xxx";
mySwap(s1,s2);
cout<<s1<<" "<<s2<<endl; //xxx hello
//mySwap("aaa","bbb"); //错误:字符串常量不可以更改
}
类模板是 C++ 中一种用于定义通用类的机制,允许我们编写可以处理不同类型数据的通用类。类模板使用参数化类型,也称为模板参数,来表示类中的成员类型、静态常量等,并在编译时生成特定类型的类。
类模板的语法如下:
template <typename T>
class ClassName {
// 类成员和函数定义
};
其中,template
关键字用于指示接下来的代码是一个模板,typename
或 class
关键字用于定义模板参数的名称(可以是任何合法的标识符),T
是模板参数的名称。
以下是一个示例,展示如何使用类模板来实现一个通用的容器类 MyContainer
:
#include
template <typename T>
class MyContainer {
private:
T value;
public:
MyContainer(T val) : value(val) {}
T getValue() const {
return value;
}
void setValue(T val) {
value = val;
}
};
int main() {
MyContainer<int> intContainer(5); // 实例化一个存储 int 类型的容器
std::cout << "Value in intContainer: " << intContainer.getValue() << std::endl;
MyContainer<double> doubleContainer(3.14); // 实例化一个存储 double 类型的容器
std::cout << "Value in doubleContainer: " << doubleContainer.getValue() << std::endl;
return 0;
}
在上面的示例中,我们定义了一个类模板 MyContainer
,它有一个模板参数 T
,用于表示容器中的值的类型。MyContainer
类具有一个私有成员变量 value
,表示存储的值,并提供了成员函数来获取和设置值。
在 main
函数中,我们实例化了两个不同类型的容器对象 intContainer
和 doubleContainer
。通过类模板,我们可以在不同的类型上重用相同的类定义,从而提供了一个通用的容器类,可以存储不同类型的值。
类模板还可以有多个模板参数,并可以使用模板参数进行类型推断、模板特化和默认模板参数等进一步扩展。类模板也可以与函数模板、模板特化和模板偏特化等相结合,以提供更强大的泛型编程能力。
需要注意的是,类模板在实例化时是根据需要生成具体的类定义,每个实例化的类是独立的,并与其他实例化的类没有直接关联。
#include
using namespace std;
template <class T>
class Demo{
private:
T value;
public:
Demo(T v):value(v){}
void set_value(T val){
value=val;
}
T get_value() const
{
return value;
}
};
int main()
{
Demo<int> d1(10); //尖括号之间指定实际参数类型
d1.set_value(20);
cout<<d1.get_value()<<endl;
string str="wander";
Demo<string> s(str);
cout<<s.get_value()<<endl;
Demo<bool> b(true);
cout<<b.get_value()<<endl; //1
}
#include
using namespace std;
template <class T>
class Demo{
private:
T value;
public:
Demo(T v);
void set_value(T val);
T get_value() const;
};
template <class T>
Demo<T>:: Demo(T v):value(v){}
template <class T>
void Demo<T>::set_value(T val){
value=val;
}
template <class T>
T Demo<T>::get_value() const{
return value;
}
int main()
{
Demo<int> d1(10); //尖括号之间指定实际参数类型 可以多种类型
1.set_value(20);
cout<<d1.get_value()<<endl;
string str="wander";
Demo<string> s(str);
cout<<s.get_value()<<endl;
Demo<bool> b(true);
cut<<b.get_value()<<endl; //1
}
泛型编程是一种编程范式,它强调编写通用、可重用的代码,以适应多种数据类型和算法需求。C++ 标准库中的 STL(Standard Template Library)就是一个基于泛型编程思想的库,提供了一组通用的数据结构和算法。
STL 包含了多个组件,其中最重要的三个组件是容器(Containers)、算法(Algorithms)和迭代器(Iterators)。这些组件都是通过使用类模板和函数模板实现的,以实现通用性和可重用性。
容器(Containers):STL 提供了多种容器类模板,如 vector、list、set、map 等。容器用于存储和管理数据,不同的容器提供不同的数据结构和操作方法,以满足不同的需求。通过使用容器,我们可以方便地管理数据集合,并提供了插入、删除、搜索等常见操作。
算法(Algorithms):STL 提供了大量的算法函数模板,如排序、查找、遍历等。这些算法函数模板可以应用于不同类型的数据集合,无需重新实现算法逻辑。通过使用算法,我们可以快速且高效地执行各种常见的数据处理操作。
迭代器(Iterators):迭代器是用于遍历容器中元素的通用接口。STL 提供了多种迭代器类型,如输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。迭代器使得我们可以以统一的方式遍历不同类型的容器,并进行元素访问和操作。
通过结合使用容器、算法和迭代器,STL 提供了一种高度可重用和灵活的编程模型,使得我们能够以一种通用的方式处理不同类型的数据和实现各种算法逻辑。这使得我们能够更加专注于问题的解决,而无需重复编写和调试通用的数据结构和算法。
使用 STL 的步骤通常是:选择合适的容器类模板,利用容器存储和管理数据;使用适当的算法函数模板对容器中的数据进行处理;通过迭代器进行容器元素的访问和操作。
需要注意的是,STL 是 C++ 标准库中的一部分,使用时需要包含相应的头文件,并使用 std::
命名空间或显式指定类和函数的名称。STL 提供了丰富的功能和高效的实现,是 C++ 中常用的编程工具之一。
在 C++ 的标准库中,顺序容器是一种用于存储和管理元素的数据结构,其中元素的顺序与其插入顺序相同。标准库提供了多个顺序容器类模板,每个容器都具有不同的特性和适用场景。以下是一些常见的顺序容器:
std::vector
:vector
是一个动态数组,可以自动调整大小以容纳所需数量的元素。它提供了快速的随机访问和在尾部插入/删除元素的能力,但在中间或开头插入/删除元素时可能效率较低。
std::list
:list
是一个双向链表,它支持在任意位置高效地插入和删除元素。但是,它不支持随机访问,只能通过迭代器进行顺序访问。
std::deque
:deque
(双端队列)是一种类似于 vector
的动态数组,但允许在两端进行高效的插入和删除操作。它提供了随机访问的能力,但相对于 vector
,在插入和删除操作时性能可能略低。
std::array
:array
是一个固定大小的数组,提供了类似于 C 风格数组的操作,但具有更多的安全性和功能。它的大小在编译时确定,无法动态调整。
这些顺序容器都提供了类似的操作接口,例如插入、删除、访问、遍历等。它们还提供了一些特定的成员函数和算法来满足不同的需求。
std::array
是 C++ 标准库中的一个顺序容器,表示一个固定大小的数组。它提供了类似于 C 风格数组的操作,并添加了额外的安全性和功能。std::array
的大小在编译时确定,无法动态调整。
std::array
的定义方式如下:
#include
std::array<ElementType, Size> arrayName;
其中,ElementType
是数组中元素的类型,Size
是数组的大小,arrayName
是数组的名称。需要注意的是,Size
必须是一个编译时常量表达式。
以下是一个示例,展示如何使用 std::array
:
#include
#include
int main() {
std::array<int, 5> myArray = {1, 2, 3, 4, 5}; // 创建一个包含 5 个整数的数组
std::cout << "Elements in array: ";
for (const auto& element : myArray) {
std::cout << element << " ";
}
std::cout << std::endl;
std::cout << "Size of array: " << myArray.size() << std::endl; // 获取数组的大小
std::cout << "First element: " << myArray[0] << std::endl; // 通过索引访问数组元素
myArray[3] = 10; // 修改数组元素的值
return 0;
}
在上面的示例中,我们使用 std::array
创建了一个包含 5 个整数的数组 myArray
。我们可以使用范围-based for 循环遍历数组的元素,并使用 size()
函数获取数组的大小。我们还可以使用索引操作符 []
访问数组的元素,并对其进行修改。
std::array
还提供了一些其他的成员函数,例如 at()
函数用于通过索引访问元素并进行边界检查,front()
和 back()
函数用于访问第一个和最后一个元素,fill()
函数用于将数组填充为指定值等。
std::array
具有与原始数组相似的性能,并且在编译时进行类型检查和边界检查,提供了更安全和方便的使用方式。因此,如果需要一个固定大小的数组并且希望使用更多的安全和功能特性,可以考虑使用 std::array
。
在 C++ 的标准库中,std::vector
是一个动态数组,也被称为向量(vector)。它是一个顺序容器,可以根据需要动态调整大小,能够自动分配和释放内存。
std::vector
的定义方式如下:
#include
std::vector<ElementType> vectorName;
其中,ElementType
是向量中元素的类型,vectorName
是向量的名称。
以下是一个示例,展示如何使用 std::vector
:
#include
#include
int main() {
std::vector<int> myVector; // 创建一个空的向量
myVector.push_back(1); // 在向量尾部插入元素
myVector.push_back(2);
myVector.push_back(3);
std::cout << "Elements in vector: ";
for (const auto& element : myVector) {
std::cout << element << " ";
}
std::cout << std::endl;
std::cout << "Size of vector: " << myVector.size() << std::endl; // 获取向量的大小
std::cout << "First element: " << myVector[0] << std::endl; // 通过索引访问向量元素
myVector[2] = 10; // 修改向量元素的值
return 0;
}
在上面的示例中,我们使用 std::vector
创建了一个空的向量 myVector
。我们可以使用 push_back()
函数在向量的尾部插入元素。通过使用范围-based for 循环遍历向量的元素,并使用 size()
函数获取向量的大小。我们还可以使用索引操作符 []
访问向量的元素,并对其进行修改。
std::vector
提供了许多其他的成员函数和操作,例如 insert()
函数用于在指定位置插入元素,erase()
函数用于删除指定位置的元素,clear()
函数用于清空向量等。
std::vector
具有动态调整大小的能力,当向量需要增加或减少元素时,会自动分配或释放内存。这使得 std::vector
成为一种方便、灵活和高效的数据结构,常用于存储和操作动态数量的元素。
需要注意的是,在大多数情况下,std::vector
是一个优秀的通用容器选择。然而,当需要频繁地在中间位置插入/删除元素时,std::list
可能更适合,因为 std::list
的插入/删除操作在时间复杂度上更为高效。
#include
#include
using namespace std;
int main()
{
vector<int> v1; //创建一个空的vector向量
v1.push_back(1);
v1.push_back(2); //1 2
//插入到第一个元素位置 下标为0
v1.insert(v1.begin(),0); //0 1 2
//插入到下标为2的位置
v1.insert(v1.begin()+2,4); //0 1 4 2
//end()是返回最后元素后面的迭代器
v1.insert(v1.end(),5); //0 1 4 2 5
//删除第一个元素
v1.erase(v1.begin()); //1 4 2 5
//删除倒数第2位置
v1.erase(v1.end()-2);
for(int i:v1){
cout<<i<<" ";
}
}
在 C++ 的标准库中,std::list
是一个双向链表,也被称为列表(list)。它是一个顺序容器,可以高效地在任意位置插入和删除元素。
std::list
的定义方式如下:
#include
std::list<ElementType> listName;
其中,ElementType
是列表中元素的类型,listName
是列表的名称。
以下是一个示例,展示如何使用 std::list
:
#include
#include
int main() {
std::list<int> myList; // 创建一个空的列表
myList.push_back(1); // 在列表尾部插入元素
myList.push_back(2);
myList.push_front(0); // 在列表头部插入元素
std::cout << "Elements in list: ";
for (const auto& element : myList) {
std::cout << element << " ";
}
std::cout << std::endl;
std::cout << "Size of list: " << myList.size() << std::endl; // 获取列表的大小
std::cout << "First element: " << myList.front() << std::endl; // 访问列表的第一个元素
myList.pop_front(); // 删除列表的第一个元素
return 0;
}
在上面的示例中,我们使用 std::list
创建了一个空的列表 myList
。我们可以使用 push_back()
函数在列表的尾部插入元素,使用 push_front()
函数在列表的头部插入元素。通过使用范围-based for 循环遍历列表的元素,并使用 size()
函数获取列表的大小。我们可以使用 front()
函数访问列表的第一个元素,并使用 pop_front()
函数删除列表的第一个元素。
std::list
还提供了许多其他的成员函数和操作,例如 insert()
函数用于在指定位置插入元素,erase()
函数用于删除指定位置的元素,clear()
函数用于清空列表等。
由于 std::list
是一个双向链表,因此在插入和删除元素方面具有高效性能。然而,它不支持随机访问,只能通过迭代器进行顺序访问。因此,在需要频繁地在任意位置插入和删除元素的情况下,std::list
是一个更适合的选择。
需要注意的是,在大多数情况下,std::vector
是一个更常用的通用容器选择,因为它支持随机访问并具有较高的效率。然而,根据具体的需求和操作,std::list
可能更适合一些特定的场景。
#include
#include
using namespace std;
int main()
{
list<int> ls;
list<string> lis ;
cout<<lis.empty()<<endl; //为空返回1
lis.push_back("Tom");
lis.push_back("Jerry"); //Tom Jerry
//lis.insert(lis.begin(),"good"); //good Tom Jerry
//vector向量不支持头部插入元素 list双向链表可以支持
lis.push_front("good"); //good Tom Jerry
//返回第一个元素
cout<<lis.front()<<endl; //good
//返回最后一个元素
cout<<lis.back()<<endl;
for(string str:lis){
cout<<str<<" ";
}
}
在 C++ 的标准库中,std::deque
是一个双端队列,也被称为队列(deque)。它是一个顺序容器,允许在两端高效地插入和删除元素。
std::deque
的定义方式如下:
#include
std::deque<ElementType> dequeName;
其中,ElementType
是队列中元素的类型,dequeName
是队列的名称。
以下是一个示例,展示如何使用 std::deque
:
#include
#include
int main() {
std::deque<int> myDeque; // 创建一个空的双端队列
myDeque.push_back(1); // 在队列尾部插入元素
myDeque.push_front(0); // 在队列头部插入元素
std::cout << "Elements in deque: ";
for (const auto& element : myDeque) {
std::cout << element << " ";
}
std::cout << std::endl;
std::cout << "Size of deque: " << myDeque.size() << std::endl; // 获取队列的大小
std::cout << "First element: " << myDeque.front() << std::endl; // 访问队列的第一个元素
myDeque.pop_front(); // 删除队列的第一个元素
return 0;
}
在上面的示例中,我们使用 std::deque
创建了一个空的双端队列 myDeque
。我们可以使用 push_back()
函数在队列的尾部插入元素,使用 push_front()
函数在队列的头部插入元素。通过使用范围-based for 循环遍历队列的元素,并使用 size()
函数获取队列的大小。我们可以使用 front()
函数访问队列的第一个元素,并使用 pop_front()
函数删除队列的第一个元素。
除了在头部和尾部插入和删除元素外,std::deque
还提供了许多其他的成员函数和操作,例如 push_back()
、pop_back()
函数用于在队列尾部插入和删除元素,back()
函数用于访问队列的最后一个元素等。
std::deque
既可以高效地在两端插入和删除元素,也支持随机访问。它的性能类似于 std::vector
,但在中间插入和删除元素时可能效率稍低。因此,当需要在两端进行频繁的插入和删除操作时,或者需要同时支持随机访问时,std::deque
是一个更适合的选择。
在C++中,迭代器(Iterator)是一种用于遍历容器中元素的对象。迭代器提供了访问容器元素的统一接口,使得可以以一种通用的方式进行元素的遍历和访问,而不依赖于容器的具体实现。
C++标准库提供了几种不同类型的迭代器,每种迭代器具有不同的功能和特性。下面介绍几种常见的迭代器类型:
输入迭代器(Input Iterator):用于从容器中读取元素的迭代器。它只支持单次遍历,每个元素只能读取一次,不能修改容器的内容。
输出迭代器(Output Iterator):用于向容器中写入元素的迭代器。它只支持单次遍历,每个元素只能写入一次,不能读取容器的内容。
前向迭代器(Forward Iterator):具有输入迭代器的所有功能,并且可以多次遍历容器。可以进行元素的读取和修改,支持自增操作符(++)。
双向迭代器(Bidirectional Iterator):具有前向迭代器的所有功能,并且支持自减操作符(–)。可以在容器中向前和向后遍历元素。
随机访问迭代器(Random Access Iterator):具有双向迭代器的所有功能,并且支持随机访问容器中的元素。可以使用索引操作符([])访问元素,支持指针算术操作(例如加法、减法),可以计算两个迭代器之间的距离。
不同类型的迭代器提供了不同的功能和操作,因此在使用迭代器时需要根据具体的需求选择合适的迭代器类型。在标准库中的容器类(如 vector、list、deque)都提供了相应的迭代器类型,可以使用容器的成员函数 begin() 和 end() 获取容器的起始和结束迭代器,用于遍历容器中的元素。
需要注意的是,C++11 引入了更强大和灵活的迭代器概念,包括正向迭代器、反向迭代器、常量迭代器等。这些迭代器类型在不同的场景下提供了更多的功能和性能优化。在使用迭代器时,应根据需要选择适当的迭代器类型,以提高代码的效率和可读性。
关联容器(Associative Containers)是 C++ 标准库中的一类容器,它们以键-值(key-value)对的形式存储和组织元素,允许根据键快速查找和访问元素。关联容器提供了高效的查找操作,并根据键的特性自动对元素进行排序。
C++ 标准库提供了以下几种常见的关联容器:
std::set
:集合容器,存储唯一的键值。元素按照键值进行自动排序,不允许重复键值。
std::multiset
:多重集合容器,存储多个相同的键值。元素按照键值进行自动排序,允许重复键值。
std::map
:映射容器,存储键-值对。键值对按照键进行自动排序,不允许重复键。
std::multimap
:多重映射容器,存储多个相同的键-值对。键值对按照键进行自动排序,允许重复键。
这些关联容器都是基于平衡二叉树(通常是红黑树)实现的,因此具有较高的查找效率和有序性。
关联容器提供了以下常用的操作:
插入元素:使用成员函数 insert()
可以向关联容器中插入键-值对。
删除元素:使用成员函数 erase()
可以根据键删除关联容器中的元素。
查找元素:使用成员函数 find()
可以根据键查找关联容器中的元素。
遍历元素:使用迭代器可以遍历关联容器中的元素。
以下是一个示例,展示如何使用 std::map
关联容器:
#include
#include
int main() {
std::map<int, std::string> myMap;
myMap.insert({1, "Alice"});
myMap.insert({2, "Bob"});
myMap.insert({3, "Charlie"});
std::cout << "Elements in map:" << std::endl;
for (const auto& pair : myMap) {
std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
}
auto it = myMap.find(2);
if (it != myMap.end()) {
std::cout << "Value found: " << it->second << std::endl;
} else {
std::cout << "Value not found" << std::endl;
}
return 0;
}
在上面的示例中,我们使用 std::map
创建了一个关联容器 myMap
,其中键的类型为整数,值的类型为字符串。通过使用 insert()
函数插入键-值对,通过使用范围-based for 循环遍历关联容器的元素,并获取键和值。使用 find()
函数可以根据键查找关联容器中的元素,如果找到了则返回指向该元素的迭代器,否则返回 end()
迭代器。在示例中,我们使用 find()
函数查找键为 2 的元素,并输出其对应的值。
需要注意的是,关联容器中的元素是按照键的顺序进行排序的,因此遍历关联容器时,元素的顺序是按照键的升序排列的。
关联容器还提供了其他的成员函数和操作,如 count()
函数用于统计指定键的元素个数,lower_bound()
和 upper_bound()
函数用于查找键的下界和上界等。
通过使用关联容器,可以方便地存储和管理键-值对,快速进行查找操作,并保持元素的有序性。选择适合的关联容器取决于具体的需求,如是否允许重复键、是否需要有序访问等。
#include
#include
using namespace std;
int main()
{
map<string,int> mp; //指定键的类型 指定值的类型
mp["age"]=18; //直接赋值
mp.insert(pair<string,int>("height",180));
pair<string,int> p("weight",70);
mp.insert(p);
//输出键所对应的值
cout<<mp["age"]<<endl; //18
//map中的键不允许重复
mp["age"]=20; //值会重新赋值
cout<<mp["age"]<<endl; //20
mp.erase("age");
//find() 找到返回迭代器位置 找不到返回的是end()
//map是支持双向迭代器 需要用!=判断是否到了末尾,不能用<
if(mp.find("age")!=mp.end()){ //输出之前先判断是否存在
cout<<mp["age"]<<endl;
}else{
cout<<"没有找到"<<endl;
}
}
迭代器(Iterator)是一种用于遍历容器中元素的对象,它提供了访问容器元素的接口,使得可以以一种通用的方式进行元素的遍历和操作,而不依赖于容器的具体实现细节。
在C++中,迭代器是标准库的一部分,它可以用于顺序容器(如 vector、list、deque)、关联容器(如 map、set)以及其他容器(如数组、字符串)等。
迭代器的主要作用是提供以下功能:
遍历容器:使用迭代器可以遍历容器中的元素,按照顺序依次访问每个元素。
访问元素:通过解引用操作符(*
)或成员访问操作符(->
)可以访问迭代器指向的元素。
修改元素:通过迭代器可以修改容器中的元素的值。
插入和删除元素:通过迭代器可以在指定位置插入或删除元素。
迭代器通常具有以下基本操作:
begin()
:返回指向容器中第一个元素的迭代器。
end()
:返回指向容器中最后一个元素之后位置的迭代器。
++
:将迭代器递增到下一个位置。
--
:将迭代器递减到上一个位置。
*
:解引用迭代器,返回迭代器指向的元素。
迭代器可以通过使用范围-based for 循环来遍历容器中的元素,也可以使用迭代器进行手动的循环遍历。
下面是一个示例,展示了如何使用迭代器遍历容器中的元素:
#include
#include
int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};
// 使用范围-based for 循环遍历容器
std::cout << "Elements in vector: ";
for (const auto& element : myVector) {
std::cout << element << " ";
}
std::cout << std::endl;
// 使用迭代器遍历容器
std::cout << "Elements in vector: ";
for (auto it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
在上面的示例中,我们使用了 begin()
和 end()
函数获取容器的起始和结束迭代器。使用范围-based for 循环时,迭代器的创建和递增是由编译器自动完成的。使用手动迭代器遍历时,我们需要创建一个迭代器并使用递增操作符来遍历容器。
#include
#include
#include
#include
using namespace std;
int main()
{
vector<int> v(5,10); //创建
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++){
cout<<*it<<" ";
}
list<string> ls;
ls.push_back("AAA");
ls.push_back("BBB");
ls.push_back("CCC");
list<string>::iterator it2;
for(it2=ls.begin();it2!=ls.end();it2++){
cout<<*it2<<" ";
}
map<string,int> mp3;
mp3.insert(pair<string,int>("one",1));
mp3.insert(pair<string,int>("two",2));
mp3.insert(pair<string,int>("threee",3));
map<string,int>::iterator it3;
for(it3=mp3.begin();it3!=mp3.end();it3++){
//这里的first代表键 second代表值
cout<<it3->first<<" "<<it3->second<<endl;
}
}