一、友元
1.1 友元概念
定义:
类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的私有成员。
作用:
在于提高程序的运行效率,但是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。导致程序维护性变差,因此使用友元要慎用。
友元使用分类:● 友元函数
● 友元类
● 友元成员函数
1.2 友元函数
友元函数是友元最常见的使用方式,是一个在类内说明,类外定义的普通函数,并不是类内的成员函数,它可以访问某个类中所有的成员。
需要注意的是:
1、友元函数没有this指针,因为它不是类内函数,参数是对象的引用(Person &p1),用于访问类中成员
2、友元的声明可以在类内的任何位置,不受权限修饰符的影响。
3、理论上友原函数可以同时访问多个类的成员,只需要在每个类中说明友元关系。
cpp
#include<iostream>
using namespace std;
//友元函数:在类内声明,类外定义的普通函数,不是类的成员函数,但却可以访问类内封装
// 的属性,有点反封装,慎用!
class Test {
private:
int a;
public:
Test(int a):a(a){ }
void show() {
cout << a << &a << endl;
}
//友元函数类内声明
friend void test_friend(Test&);
};
//友元函数类外定义
void test_friend(Test& t) {
cout << t.a << endl << &t.a << endl;
t.show();
}
//注意:
//1、友元函数没有this指针
//2、友元的声明可以在类内的任何位置,不受到权限修饰符的影响
//3、理论上友元函数可以同时访问多个类中的成员,只需要在每个类中声明友元关系
int main() {
Test t1(100);
test_friend(t1);
return 0;
}
1.3 友元类
概念:
当一个类B成为了另一个类A的"朋友"时,类A的成员可以被类B访问,这样就把类B称为类A的友元类。
需要注意的是:
● 友元关系是单向的 ,不具有交换性
● 友元关系不具有传递性
● 友元关系不能被继承
cpp
#include<iostream>
using namespace std;
//友元类
//概念:当一个类B成为另一个类A的"朋友"时,类B可以访问类A中的所有成员,此时就把类B
//成为类A的友元类。
class A {
private:
int a;
int b;
public:
A(int a, int b)
:a(a),b(b){}
//友元说明
friend class B;
};
class B {
private:
string str;
public:
B(string str) :str(str) {};
void func(A& a1) {
cout << a1.a << endl << &a1.a << endl;
a1.b++;
cout << a1.b << endl;
}
};
//注意:
//1、友元关系时单向的,不具有交换性
//2、友元关系不具有传递性
//3、友元关系不能被继承
int main() {
A a(12, 90);
B b("admin");
b.func(a);
return 0;
}
1.4 友元成员函数
可以使类B的某个成员函数成为类A的友元成员函数,这样这个成员函数就能在类A的外部访问类A的所有成员。
cpp
#include <iostream>
using namespace std;
//友元成员函数
//定义:某个类B中的一个成员函数,可以是另一个类A的友元,此时类B中的这个函数叫做类A友元成员函数,、
// 类B中的这个成员函数可以访问类A中的所有成员。
//实现步骤:
//1、先有友元成员函数所在的类B
//2、在类B中对友元成员函数进行声明
//3、在类A中,对使用friend对友元函数进行友元说明
//4、在友元成员函数中访问类A中的私有成员
class A;
class B;
//1、先有友元成员函数所在的类B
class B{
public:
//2、友元成员函数 必须类内声明,类外定义
void fn1(A& a);
};
class A{
private:
int num;
public:
//构造函数
A(int num)
:num(num){ }
//3、友成员函数的友元说明
friend void B::fn1(A& a);
};
//4、友元成员函数的类外定义
void B::fn1(A& a)
{
//访问类A的私有成员 需要称为类A的友元,需要在类A的内部用friend关键字进行说明
cout <<a.num << endl;
}
int main()
{
A a(999);
B b;//创建b对象
b.fn1(a);
return 0;
}
二、运算符重载
2.1 概念
1、C++允许把运算符看做为一个函数,因此绝大多数运算符也支持函数重载。
2、C++中预定义的运算符只能对基本数据类型进行操作,但是对于很多用户自定义的类,也需要类似的运算操作,此时就可以对这些运算符进行重载,赋予新的功能,根据新的数据类型执行特定的操作
3、运算符重载的本质是函数重载,它也是C++多态的一种体现
4、运算符重载增强了C++的可扩充性,使得C++代码更加直观、易读
C++提供的运算符重载机制,重载运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要重载的运算符共同组成。和其他函数一样,重载运算符的函数也包括返回类型、参数列表及函数体。
可以被重载的运算符:算术运算符:+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不能被重载的运算符:
成员运算符"."、指针运算符"*"、三目运算符"? :"、sizeof、作用域"::"
运算符重载既可以通过友元函数实现,又可以通过成员函数函数。
2.2 友元函数运算符重载
cpp
#include <iostream>
using namespace std;
//运算符重载
//相关概念
//1、运算符在C++中可以被看做函数,因此可以重载
//2、C++中的运算符,只对基本类型进行操作,对用户自定义的类型不能操作,而有时用户自定义的类型有运算的需求,
// 这时可以通过运算符重载来解决。
//3、运算符的重载本质就是函数重载,可以扩充功能,是多态的一种体现。
class Test{
private:
int num;
public:
Test(int num)
:num(num){ }
//num读接口
int get_num() const
{
return this->num;
}
//+运算符重载的友元说明
friend Test operator +(const Test& t1,const Test& t2);
//前置++运算符重载友元说明
friend Test operator ++(Test& t);
//后置++ 运算符重载友元说明
friend Test operator ++(Test& t,int);
};
int main()
{
int a = 2;
int b = 3;
int c = a+b; //正常 基本的数据类型可以使用+运算符进行操作
cout << c << endl;
string st1="admin";
string st2="murphy";
string st3 = st1+st2; //正常 内部已经进行过重载了
cout << st3 << endl;
//+
Test t1(1);
Test t2(30);
Test t3 = t1+t2;//报错 需要手动进行运算符重载
cout << t3.get_num() << endl;
//前置++
int num1 =9;
int num2 = ++num1;
cout << num2 << endl;
cout << num1 << endl;
Test t4(99);
Test t5 = ++t4;//需要对前置++ 运算符重载
cout << t5.get_num() << endl;//100
cout << t4.get_num() << endl;//100
//后置++
Test t6(5);
Test t7=t6++;
cout << t6.get_num() << endl; //6
cout << t7.get_num() << endl; //5
/*
怎么重载? 定义同名函数,根据参数类型、个数加以区分
1、函数名 运算符函数名 "operator 运算符" 例如:+ 函数名: operator +
2、参数类型 对象类型
3、参数返回值 对象类型
4、函数体逻辑代码:按需求进行写
5、实现方式: 通过友元函数函数实现
*/
return 0;
}
//+ 运算符重载
Test operator +(const Test& t1,const Test& t2){
//需要访问t1的属性 怎么办??? 成为友元函数
Test newt(0);
newt.num = t1.num +t2.num;
return newt;
//代码优化
//return t1.num + t2.num;
}
//++ 自增运算符重载 前置++(单目运算符)
Test operator ++(Test& t)
{
//先加1 再返回
t.num++;
return t;
//代码优化
//return ++t.num;
}
//后置++ 运算符重载 int是一个占位符,用来和前置++
Test operator ++(Test& t,int)
{
//先返回 再加1
// Test temp(t); //拷贝对象 int temp = a;
// t.num++;
// return temp;
//代码优化
return t.num++;
}
//作业: 对 > 进行运算符重载 可以计算 t5 > t6=====> 返回布尔值
2.3 成员函数运算符重载
成员函数,默认都有一个隐含的this指针,可以比友元函数重载少一个参数,隐含的this指针作为成员函数的第一个参数存在。
cpp
#include <iostream>
using namespace std;
class Test{
private:
int num;
public:
//构造函数
Test(int num):
num(num){ }
//num对外接口
int get_num()
{
return this->num;
}
int fn1(int a){
cout << a << endl;
}
//类内成员函数 进行运算符重载
Test operator ++(); //前置++
//成员函数运算符重载 +
Test operator +(const Test& t);
//后置++
Test operator ++(int);
};
//前置++
Test Test::operator ++()
{
// this->num++;
// return *this;
//优化代码
return ++this->num;
}
//+
Test Test::operator +(const Test& t)
{
// Test newt(0);
// newt.num = this->num+t.num;
// return newt;
//优化
return this->num + t.num;
}
//后置++
Test Test::operator ++(int)
{
//先返回值 再+1
// Test t(*this);
// this->num++;
// return t;
//代码优化
return this->num++;
// }
int main()
{
int a=1;
int b=2;
int c = a+b;
cout << c << endl;
//运算符重载 也可以使用类的成员函数进行运算符重载
Test t4(99);
++t4;//报错需要重载
cout << t4.get_num() << endl;
//+
Test t1(1);
Test t2(2);
Test t3 = t1+t2;
cout << t3.get_num() << endl;
//后置++
Test t5 = t3++;
cout << t5.get_num() << endl;//3
cout << t3.get_num() << endl;//4
return 0;
}
2.4 特殊情况
2.4.1 赋值运算符重载
赋值运算符重载 = ,只能使用成员函数运算符重载, 当程序员在一个类中不手写赋值运算符重载时,编译器会自动添加,默认的赋值运算符重载函数,其内容类似于拷贝构造函数。
作用:用于把一个对象的值赋值给另一个对象
形式:T& operator =(const T& t) //T可以是任意类型
特点:是类内函数,只支持成员函数重载
不显示给出赋值运算符函数时,编译器会给出默认的赋值运算符函数,完成对象间的赋值。
但是当属性有指针类型的时候,这时需要显示写出赋值运算符函数,类似于浅拷贝出现的问题
cpp
#include<iostream>
using namespace std;
//赋值远算符重载
class Integer{
private:
int number;
public:
//构造函数
Integer(int number)
:number(number){}
//常成员函数 用于访问成员变量
int get_number() const{
return number;
}
//编译器自动添加的赋值远算符重载
Integer& operator =(const Integer& t){
this->number = t.number;
return *this;
}
};
//注意:
//当成员变量出现指针时,需要程序员手动编写赋值运算符重载逻辑,防止浅拷贝出现
int main(){
//测试
Integer t1(0);
Integer t2(3);
cout << t2.get_number() << endl;//3
t2 = t1;// 赋值运算符
cout << t2.get_number()<< endl;//0
return 0;
}
注意,当成员变量出现指针时,需要程序员手动编写赋值运算符重载逻辑,防止浅拷贝出现。
手写赋值运算符重载逻辑,防止出现浅拷贝
cpp
#include <iostream>
#include <string.h>
using namespace std;
class Test{
private:
int num;
char *name;
public:
// Test(int num,char *name)
// :num(num),name(name)
// { }
//构造函数 深拷贝
Test(int num,char *name)
{
this->num = num;
this->name = new char[20];
strcpy(this->name,name); //注意:不能直接写等号!!!
}
//手动添加编译器自带赋值运算符重载 浅拷贝
// Test& operator =(const Test& t)
// {
// this->num = t.num;
// this->name = t.name;//简单的地址值复制
// cout << "******" << endl;
// return *this;
// }
//手写赋值运算符重载 实现深拷贝
Test& operator =(const Test& t)
{
this->num = t.num;
this->name = new char[20];
strcpy(this->name,t.name);
cout << "*******%%%%%%%%%%%%%" << endl;
return *this;
}
//num 读接口
int get_num() const
{
return this->num;
}
//show方法
void show() const
{
cout << this->name << endl;
}
//name读接口
char *get_name() const
{
return this->name;
}
};
int main()
{
char u[20] = "admin";
Test t1(99,u);
Test t2(30,"hhhh");
t2=t1; //赋值运算符不会报错 原因编译器默认自带了一个赋值运算符重载
cout << t2.get_num() << endl;
cout << t2.get_name() << endl;
//编译器默认的赋值运算符重载,不安全 当成员属性是指针类型时会出现浅拷贝的情况
strcpy(t1.get_name(),"tttttt");
cout << t1.get_name() << endl;
cout << t2.get_name() <<endl;
cout << "$$$$$$$$$$$$$$$$444"<< endl;
strcpy(u,"wwww");
cout << t1.get_name() << endl;
cout << t2.get_name() <<endl;
return 0;
}
2.4.2 类型转换运算符重载
这种情况的写法比较特殊,且必须使用成员函数运算符实现重载。
作用:把自定义类型转成任意类型
特点:
1、只支持成员函数重载
2、不需要写返回值类型
cpp
#include <iostream>
using namespace std;
class Test{
private:
int num;
public:
Test(int num)
:num(num)
{
}
//num 读接口
int get_num() const
{
return num;
}
//(int)t 转成整形的运算符重载
operator int()
{
return this->num;
}
// 思考如果转成其他类型怎么办?需要再次重载 例如 (string)t
};
int main()
{
Test t1(100);//创建对象
Test t2 = 90;//隐式调用构造函数 也会创建对象
cout << t2.get_num() << endl;
int b(t1); //会出现类型转换 把对象类型t转换成整形
int res=(int)t2;//强制类型转换
//cout << t << endl
cout << res << endl;
cout <<b << endl;
return 0;
}
2.4.3 运算符重载的总结
● 运算符重载只能限制在C++已有的运算符范围内,即不能创建新的运算符。
● 运算符重载不能改变运算符的优先级和结合性。
● 运算符重载不能改变运算符的操作数和语法结构。
● 无法更改已有的基本数据类型运算规则,只能应用于包含用户自定义类型的运算。
● 运算符重载应该保持与原有运算符功能类似,避免没有目的地滥用运算符重载。
● 运算符重载函数不支持参数默认值的设定。
● 一般情况下,单目运算符建议使用成员函数重载,双目运算符使用友元函数重载
三、std::string类
string实际上是C++内置的一个类,内部对char *进行了封装,不用担心数组越界问题,string类中,除了上课讲解的函数外,还有很多函数可以使用,可以自行查阅文档。
3.1 构造函数原型:
string(); //创建一个空的字符串 例如: string str;
string(const char* s); //使用字符串s初始化
string(const string& str); //拷贝构造函数
string(int n, char c); //使用n个字符c初始化
string(char *str,int n);//保留前n个字符组成字符串
3.2 常用函数
str.size() //取字符串的长度
str.length() //同上取字字符串的长度
str.empty() //判断字符串是否为空
str1==str2 //判断字符串是否相等
3.3 字符串的追加
string& operator+=(const char* str); //重载+=操作符
string& operator+=(const char c); //重载+=操作符
string& operator+=(const string& str); //重载+=操作符
string& append(const char *s); //把字符串s连接到当前字符串结尾
string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s); //同operator+=(const string& str)
string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾
3.4取字符
char& operator[](int n); //通过[]方式取字符
char& at(int n); //通过at方法获取字符
3.4.1 插入
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
3.4.2删除
string& erase(int pos, int n = npos); //删除从Pos开始的n个字符
string& substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串。注意接收
3.4.3替换
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串
cpp
#include<iostream>
#include<string.h>
using namespace std;
int main(){
//复习之前讲的string函数
string str1 = "admin";
char str2[23]="9999";
cout << str1.at(0) <<endl;
cout << str1[0] << endl;
cout << "字符串长度0" << str1.size() << endl;
cout << "字符串长度" <<str1.length() <<endl;
//字符串的遍历
for(char c:str1){
cout << c << endl;
}
cout << "*****" << endl;
for(int i=0;i<str1.size();i++){
cout << str1[i] << endl;
}
//string实际上是C++内置的一个类,内部对char *进行了封装,不用担数组越界问
//类里面有很多方法
string str3 = "welcome"; //隐式调用构造函数
cout << str3 << endl;
string str4("hello");//显示调用构造函数
cout << str4 << endl;
// 创建一个堆内存对象
string *str5 = new string("world");
cout << *str5 << endl;
delete str5;
string str6(str4);//调用拷贝构造函数
cout << str6 << endl;
string str7(3,'a');//使用3个字符初始化字符串
cout <<str7 <<endl;
string str8("ASDFFGGG",3);//保留3个字符组成字符串
cout << str8 << endl;
cout << str8.empty() << endl; //判断字符串是否为空
cout << (str7 == str8) << endl;//判断字符串是否相等
//字符串的追加
string str9("qqqq");
string str10("mmmmm");
str9+=str10;
cout << str9 << endl;
str9+="hhhhhh";
cout << str9 << endl;
//字符追加
str9+='u';
cout << str9 <<endl;
char *str11 = "99999";
str9 += str11;
cout << str9 <<endl;
//字符串的拼接
string str12("aaaa");
str12.append("wwwww");
cout << str12 <<endl;
//将字符串的前n个字符拼接到目标字符串的结尾
//参数1 从哪个字符串 里选取字符进行拼接
//参数2 选取前几个字符
str12.append("werttyu",3);
cout << str12 << endl;
//将字符串的s中从某个字符开始的n个字符拼接到目标字符串的结尾
//参数1 从那个字符串里选取字符进行拼接
//参数2 从哪开始
//参数3 拼接几个字符
str12.append("mnbvc",1,3);
cout << str12 << endl;
//插入
//参数1 从哪开始插
//参数2 插入什么
string str13="asdf";
str13.insert(2,"nnn"); //在指定位置插入字符串
cout << str13 << endl;//asnnndf
//删除(截取)
//参数1 从哪开始删除(截取)
//参数2 删除(截取)几个字符
string str14="asdfg";
cout << str14.erase(2,3) << endl;//"as"
string str15="nhytgbvfr";
cout << str15.substr(2,4) << endl;//"ytgb"
//替换
//参数1 从哪开始替换
//参数2 替换几个字符
//参数3 替换为什么
string str16="mnbvc";
cout << str16.replace(0,3,"kkk") << endl; //"kkkvc"
return 0;
}
练习:自定义一个String类(注意是大写,不是自带的string),要求实现下面的功能
功能:比较字符串的长度(> < == ) , 字符串的拼接功能
cpp
class String{
private:
string str;
public:
String(const string &s); //构造函数
//友元运算符重载
friend String operator +(const String &s1,const String &s2);
friend bool operator >(const String &s1,const String &s2);
friend bool operator <(const String &s1,const String &s2);
friend bool operator ==(const String &s1,const String &s2);
//成员函数运算符
bool operator !=(const String &other);
//赋值运算符
String& operator =(const String &s);
// 类型转换
operator string(); //String转string
//成员函数
string get_str() const;
};
cpp
#include <iostream>
#include <cstring>
using namespace std;
class String{
private:
string str;
public:
String(const string &s); //构造函数
//友元运算符重载
friend String operator +(const String &s1,const String &s2);
friend bool operator >(const String &s1,const String &s2);
friend bool operator <(const String &s1,const String &s2);
friend bool operator ==(const String &s1,const String &s2);
//成员函数运算符
bool operator !=(const String &other);
//赋值运算符
String& operator =(const String &s);
// 类型转换
operator string(); //String转string
//成员函数
string get_str() const;
};
//构造函数
String::String(const string &s)
{
str = s;
}
//类型转换
String::operator string()
{
return str;
}
//访问属性str
string String::get_str() const
{
return str;
}
///字符串赋值
String& String::operator =(const String &s)
{
str=s.str;
return *this;
}
//字符串长度判等
bool String::operator !=(const String &other)
{
return str.size() != other.str.size();
}
//字符串拼接
String operator +(const String &s1,const String &s2)
{
return s1.str+s2.str;
}
//字符串长度 >
bool operator >(const String &s1,const String &s2)
{
return s1.str.size()>s2.str.size();
}
//字符串长度 <
bool operator <(const String &s1,const String &s2)
{
return s1.str.size()<s2.str.size();
}
//字符串长度 ==
bool operator ==(const String &s1,const String &s2)
{
return s1.str.size()==s2.str.size();
}
int main()
{
String s1("qwe");
String s2("qwe");
String s3("QWE");
String s4("1234");
String s5("12");
String s6("5678");
String s7("34");
if(s1>s2)
cout<<"正确"<<endl;
else
cout<<"错误"<<endl;
if(s1>s3)
cout<<"正确"<<endl;
else
cout<<"错误"<<endl;
if(s1>s4)
cout<<"正确"<<endl;
else
cout<<"错误"<<endl;
cout<<(s2+s3).get_str()<<endl;
s2=s3;
cout<<s2.get_str()<<" "<<s3.get_str();
return 0;
}
四、模版和泛型编程
4. 1 模板
概念:
模板可以让类或者函数支持一种通用类型,在编写时不指定固定的类型,在运行时才决定是什么类型,理论上讲可以支持任何类型,提高了代码的重用性。
模板可以让程序员专注于内部算法而忽略具体类型,是泛型编程的基础。
模板通常由两种实现方式:
●函数模板
函数模板可以使一个函数支持任意数据类型。
● 类模板
类模板可以使一个类内部支持任意数据类型。
4.1.1 函数模板
1、tempplate 定义模板的关键字
typename 和 class意义相同,后面跟的变量参数,代表任意类型//一般地,typename常用于函数模版,class常用于类模版
cpp
#include<iostream>
using namespace std;
//函数模板
//通俗的讲就是,在函数定义的时候,不去指定具体的类型(包括返回值类型和参数类型),
//运行时,才去指定类型,提高了代码的重用性
//函数模版:
//template 模板的关键字
//typename 和 class意义相同,后面跟的变量参数,代表任意类型
//一般地,typename常用于函数模版,class常用于类模版
template <typename T>
//换一种写法:
//template <class T >
//封装一个函数
T add(T a,T b){
return a+b;
}
int main(){
cout << add(1,2) << endl; //T是 int
cout << add(3.2,4.5) << endl; //T是double
string str1="admin";
string str2 = "hhhhhh";
cout << add(str1,str2) << endl; //T是string
return 0;
}
4.1.2 类模板
类模板的注意:
类模板不支持自动类型推导,创建对象的同时,需指明实际的参数类型
cpp
#include<iostream>
using namespace std;
//类模板
//类模板类型声明
template <class T>
class Demon{
private:
T value;
public:
//构造初始化列表
Demon(T value):value(value){ }
//value属性的读接口
T get_value() const{
return value;
}
//value属性的写接口
void set_value(T value){
this->value = value;
}
};
int main(){
//测试
//类模板不支持类型推导,创建对象时须指明实际的参数类型
//Demon d1(100);//报错 须指明实际的参数类型
Demon<int> d1(100);
cout << d1.get_value() << endl;
d1.set_value(99);
cout << d1.get_value() << endl;
Demon<string> d2("admin");
cout << d2.get_value() << endl;
d2.set_value("murphy");
cout <<d2.get_value() <<endl;
// 堆内存对象
Demon<int>* d3 = new Demon<int>(3);
cout << d3->get_value()<<endl;
delete d3;
d3=NULL;
return 0;
}
注意:上面的代码如果要写成类内声明,类外定义的形式,代码如下:
cpp
#include<iostream>
using namespace std;
//类模板
template <class T>
class Demon{
private:
T value;
public:
//构造初始列表声明
Demon(T value);
//成员函数声明
T get_value() const;
void set_value(const T& value);
};
//类外定义构造初始化列表
template <typename T>
Demon<T>::Demon(T value):value(value){ }
template <typename T>
T Demon<T>::get_value() const{
return this->value;
}
template <typename T>
void Demon<T>::set_value(const T& value){
this->value = value;
}
int main(){
Demon<int> d1(1);
cout << d1.get_value() << endl;
d1.set_value(2);
cout << d1.get_value() << endl;
Demon<string> d2("admin");
cout << d2.get_value() << endl;
d2.set_value("murhy");
cout <<d2.get_value() <<endl;
Demon<int>* d3 = new Demon<int>(99);
cout << d3->get_value() << endl;
d3->set_value(55);
cout <<d3->get_value() << endl;
return 0;
}
4.2 容器
4.2.1 泛型编程
泛型编程提出的目的是:发明一种语言机制,能够实现一个标准的容器库(标准模板库 STL),标准容器库可以做到编写一般化的算法,**来支持不同的数据类型。**例如模板的应用就是泛型编程的基础。
4.2.2 标准模板库(STL)
标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。虽说它主要表出现到C++中,但在被引入C++之前该技术就已经存在了很长时间。
STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和 iterator(迭代器),几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
● 算法: 例如:swap交换变量函数 sort排序
● 容器 :存放数据的类模板
● 迭代器:指示元素位置。方便算法访问操作容器
4.2.3 容器
相关概念:
1、容器是用来存储数据的集合,数据元素可以是任何类型。
2、容器类只能使用栈内存,不能使用new和delete关键字。
3、容器类的使用需要引入对应的头文件。
容器分为:顺序容器 和 关联容器
●顺序容器: 元素之间顺序关系,元素有固定位置,典型的代表有Array数组、vector向量、list列表、deque队列
●关联容器: 元素之间没有严格物理上的顺序关系,内部会做相应排序,典型的代表,map键值对, mulitmap多重键值对
4.2.4 顺序容器
4.2.4.1 数组 Array
array是C++11新增的数组类型,与传统数组相比更加安全、易于使用
cpp
#include<iostream>
#include<array>
using namespace std;
//标准模板库广义上分为三部分:算法、容器、迭代器
//容器分为两类:顺序容器和关联容器
//顺序容器:数组array 、向量vector、列表list、队列deque
//1、数组array 是C++新增的一种数组类型,与传统数组(C语言中的数组)相比更加安全、易于使用
//注意:容器类的使用需要引入对应的头文件
int main(){
//创建一个长度为5的整型数组,并初始化
int arr[5] = {1,2,3,4,5}; //传统数组
array<int,5> arr2 = {1,6,8};//新型数组
//访问数组元素
cout << arr2[0] <<endl;
cout << arr2[4] <<endl;//未初始化的默认为0
cout << arr2[88] << endl;//随机值
cout <<arr2.at(1) << endl;//6
//修改数组元素
arr2[0]=99;
cout << arr2[0] << endl;
//数组长度
cout << "数组长度:" << arr2.size() << endl;
//cout << "数组长度:" << arr2.length() <<endl; 注意:没有length方法
//数组遍历方法一:
for(int i=0;i < arr2.size();i++){
//cout << arr2[i] << endl;
cout << arr2.at(i) << endl; //效果 同上
}
//数组遍历方法二:
for(int i:arr2){
cout << i << endl;
}
// 迭代器遍历,先记语法 后边再理解原理
return 0;
}
4.2.4.2 向量 vector
vector向量容器是一种支持高效的随机访问和高效尾部插入新元素的容器
向量容器内部由数组实现,比较适合随机存取和尾部插入操作,不适合删除和随机插入操作
cpp
#include<iostream>
#include<vector>//向量头文件
using namespace std;
//向量vector 是一种支持高效的随机访问和高效的尾部插入的新型容器
//内部是由数组实现的。
int main(){
//创建一个空的整型向量容器
vector<int> vec1;
//判断向量是否为空
cout << vec1.empty() << endl;//1
//创建一个长度为5的整型向量容器
vector<int> vec2(5);
//访问向量元素
cout << vec2[0] << endl;
cout << vec2.at(1) << endl;
//修改向量元素
vec2[0] =99;
cout << vec2[0] <<endl;
//判断是否为空
cout << vec2.empty() << endl;
//尾部追加元素
vec2.push_back(88);
cout << vec2[5] << endl;
//遍历向量容器
for(int i=0;i<vec2.size();i++){
cout << vec2[i] << endl;
}
cout << "********8888" << endl;
vec2.push_back(77);
for(int i:vec2){
cout << i<< endl;
}
//插入操作
vec2.insert(vec2.begin()+1,777);//在第2个位置插入777
for(int i:vec2){
cout << i<< endl;
}
cout<< "%%%%%%%%%%%%%%%%%%" << endl;
vec2.insert(vec2.end()-1,5);//在倒数第2个位置插入5
for(int i:vec2){
cout << i<< endl;
}
cout << "向量容器的长度:" << vec2.size() << endl;
//删除
//删除最后一个元素
vec2.pop_back();
for(int i:vec2){
cout << i<< " ";
}
cout << endl;
// 删除第1个元素
vec2.erase(vec2.begin());
for(int i:vec2){
cout << i<< " ";
}
cout << endl;
//删除第3个元素
vec2.erase(vec2.begin()+2);
for(int i:vec2){
cout << i<< " ";
}
cout << endl;
//删除倒数第2个元素
vec2.erase(vec2.end()-2);
for(int i:vec2){
cout << i<< " ";
}
cout << endl;
// 清空容器
vec2.clear();
cout << "向量容器的长度:" << vec2.size() << endl;
//迭代器遍历,后面再讲
return 0;
}
4.2.4.3 列表 list
list列表内部是由双链表 实现,内存空间不连续,不支持下标,可以高效的进行插入和删除操作, 不支持随机访问 。vector向量的方法在list双向链表里同样适用,但注意。list不可以通过[]和at方法访问。
cpp
#include<iostream>
#include<list>
using namespace std;
//列表 list 内部是由双向链表实现的,内存空间不连续,不支持下标,可以进行高效的插入和删除操作。
// 适合随机访问。
int main(){
//1、创建一个空列表
list<int> list0(0);
cout << "列表长度:" << list0.size() << endl;
//2、列表 判空
cout << "列表是否为空:" << list0.empty() << endl;
//3、创建一个长度为5的字符串列表,每个元素都是"hello"
list<string> list1(5,"hello");
//4、获取列表长度
cout << "列表长度:" << list1.size() << endl;
cout << "列表是否为空:" << list1.empty() << endl;
//5、列表遍历 注意:不支持索引遍历
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//6、插入
//6.1、末尾追加元素
list1.push_back("admin");
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//6.2、头部插入元素
list1.push_front("murphy");
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//6.3、在第2个位置插入元素
list1.insert(++list1.begin(),"mmmm");
list1.push_front("murphy");
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//6.4、在第3个位置插入元素
//list1.insert(list1.begin()+2,"mmmm"); //注意:不行!!! 因为列表不是顺序存储的
//(1)、保存迭代器指针
list<string>::iterator it1 = list1.begin();
//(2)、移动指针到到第3个元素为止
advance(it1,3);
//(3)、插入元素
list1.insert(it1,"bbb");
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//7、删除
//7.1、删除第1个元素
list1.pop_front();
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//7.2、删除最后一个元素
list1.pop_back();
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//7.3、删除倒数第2个元素
//(1)保存迭代器指针
list1.push_back("xxxx");
list1.push_back("yyyyyy");
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//(1)保存迭代器指针
list<string>::iterator iter=list1.end();
iter--;
cout << *iter << endl;//倒数第一个元素
iter--;
cout << *iter << endl;//倒数第二个元素
//(2)删除倒数第2个元素
list1.erase(iter);
for(string c:list1){
cout <<c << " ";
}
cout <<endl;
//7.4 返回第1个元素的引用
cout << list1.front() << endl;
//7.5 返回最后一个元素
cout << list1.back() << endl;
//7.6列表清空
list1.clear();
cout << "列表长度:" << list1.size() << endl;
cout << "列表是否为空:" << list1.empty() << endl;
return 0;
}
4.2.4.4 队列 deque
deque几乎支持所有vector和list的API,性能位于二者之间
cpp
#include<iostream>
#include<deque>
using namespace std;
//队列deque
//1、内部是一个双向数组,可以进行高效头插和头删
//2、几乎可以使用list和vector的所有方法
int main(){
//1、初始化一个空队列
deque<int> dq1;
//2、队列长度
cout << "队列长度:" <<dq1.size() <<endl;
//3、队列判空
cout <<"队列是否为空:" <<dq1.empty() << endl;
//4、初始化个2个元素的int队列
deque<int> dq2(2,5);
cout << "队列长度:" <<dq2.size() <<endl;
deque<string> dq3(3,"admin");
cout << "队列长度:" <<dq3.size() <<endl;
//5、 访问队列元素
cout << dq2[0] << endl;
cout << dq3.at(2) <<endl;
//6、遍历队列元素
for(int i=0;i<dq2.size();i++){
cout << dq2[i] << " ";
}
cout << endl;
for(int i:dq2){
cout << i << " ";
}
cout <<endl;
//7、迭代器遍历
dq2.push_front(9);
dq2.push_back(8);
deque<int>::iterator it1=dq2.begin();
cout << *it1 << endl;
it1 = dq2.end();
cout << *it1 << endl;
cout <<*(--it1) <<endl;
cout << dq2.back() << endl;
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout <<endl;
//8、插入
//头插
dq2.push_front(1);
//尾插
dq2.push_back(2);
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout <<endl;
//随机插 在第3个位置插
dq2.insert(dq2.begin()+3,55);
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout <<endl;
//在倒数第2个位置插 44
dq2.insert(dq2.end()-1,44);
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout <<endl;
//9、删除
//尾删
dq2.pop_back();
//头删
dq2.pop_front();
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout << endl;
// 随机删 删第2个元素
dq2.erase(dq2.begin()+2);
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout << endl;
// 随机删 删倒数第2个元素
dq2.erase(dq2.end()-2);
for(deque<int>::iterator it=dq2.begin();it !=dq2.end();it++){
cout << *it << " ";
}
cout << endl;
//10、访问头元素 尾元素
cout<<dq2.back() <<endl;
cout <<dq2.front() << endl;
return 0;
}
4.5 关联容器
map容器
关联容器在内部其实是有序的,但是在外部使用时不能认为其有顺序。
1、map容器里的元素都是一组键值对,键值对的键(key)是唯一的,通常是string类型,键值对的值(value)可以是任意类型。
2、键值对用关键字pair创建,若干个键值对组成一个map容器。
3、键值对有两种创建方式:
●pair p1 (key1,value1);
●pair p2 = make_pair(key2,value2);
4、map的创建方式:
map <string,int> mp1;
cpp
#include<iostream>
#include<map>
using namespace std;
//map容器,内部其实是有序的,但是在外部使用时不能认为其有序。
//1、map容器由一组键值对(pair)组成,键值对的键(key)必须是唯一,一般
//字符串类型,键值对的值可以是任意类型
//2、键值对使用关键字pair创建,若干个键值对组成一个map容器
//3、键值对的创建方式有两种:
// (1)pair <string,int> p1(key1,value1);
// (2)pair <string,int> p2 = make_pair(key2,value2);
//4、map的创建方式: map <string,int> map1;
int main(){
//1、创建键值对
pair <string,int> p1("age",18);
pair <string,int> p2("weight",70);
//2、输出键值对的键
cout << p1.first << endl;//age
//3、输出键值对的值
cout <<p1.second << endl;//18
//4、创建容器
map <string,int> mp1;
cout <<"map的长度:" <<mp1.size() <<endl;
cout << "是否为空:" <<mp1.empty() <<endl;
//5、向容器里插入元素(键值对),3种插入方式
mp1.insert(p1);
mp1.insert(pair <string,int>("height",90));
mp1["counter"] = 9;
//6、map的遍历,不能通过索引,只能通过迭代器进行遍历
map<string,int>::iterator iter = mp1.begin();
for(;iter!=mp1.end();iter++){
cout << iter->first << ":" << iter->second << " ";
}
cout << endl;
//7、修改map元素的值
mp1["counter"] = 99;
//8、根据键名,输出相对应的值
cout << mp1["counter"] << endl;
//9、判断map是否为空
cout << "是否为空:" <<mp1.empty() <<endl;
cout <<"map的长度:" <<mp1.size() <<endl;
//10、删除指定元素 可以根据返回值判断是否删除成功
int res = mp1.erase("name");
cout << (res?"已删除":"未删除") << endl;
res = mp1.erase("age");
cout << (res?"已删除":"未删除") << endl;
//遍历测试
iter = mp1.begin();
for(;iter!=mp1.end();iter++){
cout << iter->first << ":" << iter->second << " ";
}
cout << endl;
//11、map清空
mp1.clear();
cout << "是否为空:" <<mp1.empty() <<endl;
cout <<"map的长度:" <<mp1.size() <<endl;
return 0;
}
4.6 迭代器
1、迭代器是一个特殊的指针,主要用于容器的元素读写以及遍历。
2、如果使用迭代器不进行修改操作,建议使用只读迭代器const_iterator,反之则使用iterator。
3、迭代器是最高效的遍历方式
cpp
#include<iostream>
#include<string.h>
#include<array>
#include<list>
#include<vector>
#include<map>
using namespace std;
//迭代器,是一种特殊的指针,主要用于元素容器的读写及遍历
//推荐用法:如果迭代器不进行元素的修改,建议使用只读迭代器const_iterator
//迭代器是最高效的遍历方式
int main(){
//1、使用迭代器遍历字符串
string str="admin";
string::const_iterator iter = str.begin();
for(;iter!=str.end();iter++){
cout << *iter << " " ;
}
cout << endl;
//2、使用迭代器遍历数组
array<int,5> arr={1,2,3,4,5};
for(array<int,5>::const_iterator iter=arr.begin();iter!=arr.end();iter++){
cout << *iter << " ";
}
cout << endl;
//3、使用迭代器遍历向量
vector<int> vec(5,2);
for(vector<int>::iterator iter=vec.begin();iter!=vec.end();iter++){
cout << *iter << " ";
}
cout << endl;
//4、使用迭代器遍历列表
list<int> list1(5,4);
for(list<int>::iterator iter=list1.begin();iter!=list1.end();iter++){
cout << *iter << " ";
}
cout << endl;
//5、使用迭代器遍历map
map<string,string> mp1;
mp1["name"]="admin";
mp1["addr"]= "北京";
mp1.insert(pair<string,string>("hobby","敲代码"));
for(map<string,string>::iterator iter=mp1.begin();iter!=mp1.end();iter++){
cout << iter->first << ":" << iter->second << " ";
}
cout <<endl;
}