::解决归属问题(局部变量与全局变量重名时,函数内部使用全局变量,可以使用::作用域运算符)
创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。
c++允许我们对名字的产生和名字的可见性进行控制。 我们之前在学习c语言可以通过static关键字来使
得名字只得在本编译单元内可见,在c++中我们将通过一种通过命名空间来控制对名字的访问。
数据类型的别名typedef
typedef 原数据类型名 别名;
C++11还可以用using关键字创建数据类型的别名。
using 别名=原数据类型名;
在c++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称
互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程
序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
命名空间的本质就是封装
1、创建一个命名空间:
namespace A{int a = 10;
}
namespace B{int a = 20;
}
void test(){
cout << "A::a : " << A::a << endl;//10
cout << "B::a : " << B::a << endl;//20
}
2、命名空间只能全局范围内定义(以下错误写法)
void test(){namespace A{int a = 10;}namespace B{int a = 20;}cout << "A::a : " << A::a << endl;cout << "B::a : " << B::a << endl;
}
3、命名空间可嵌套命名空间
namespace A{int a = 10;namespace B{int a = 20;}
}
void test(){cout << "A::a : " << A::a << endl;cout << "A::B::a : " << A::B::a << endl;
}
4、命名空间是开放的,即可以随时把新的成员加入已有的命名空间中
namespace A{int a = 10;
}
//命名空间重名会优化成一个空间
namespace A{void func(){cout << "hello namespace!" << endl;}
}
void test(){cout << "A::a : " << A::a << endl;A::func();
}
5、声明和实现可分离
函数前面可以填作用域也可以填函数返回值
namespace MySpace{//声明void func1();void func2(int param);
}
void MySpace::func1(){cout << "MySpace::func1" << endl;
}
void MySpace::func2(int param){cout << "MySpace::func2 : " << param << endl;
}
6、无名命名空间
意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内部连接
namespace{int a = 10;void func(){ cout << "hello namespace" << endl; }
}
void test(){cout << "a : " << a << endl;func();
}
7、命名空间别名
namespace veryLongName{int a = 10;void func(){ cout << "hello namespace" << endl; }
}
void test(){namespace shortName = veryLongName;cout << "veryLongName::a : " << shortName::a << endl;veryLongName::func();shortName::func();
}
namespace A{int paramA = 20;int paramB = 30;void funcA(){ cout << "hello funcA" << endl; }void funcB(){ cout << "hello funcA" << endl; }
}
void test(){//1. 通过命名空间域运算符cout << A::paramA << endl;A::funcA();//2. using声明成员可用using A::paramA;using A::funcA;cout << paramA << endl;//cout << paramB << endl; //不可直接访问funcA();//3. 同名冲突int paramA = 20; //相同作用域注意同名冲突
}
using声明成员碰到函数重载
namespace A{void func(){}void func(int x){}int func(int x,int y){}
}
void test(){using A::func;func();func(10);func(10, 20);
}
如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合
。
namespace A{int paramA = 20;int paramB = 30;void funcA(){ cout << "hello funcA" << endl; }void funcB(){ cout << "hello funcB" << endl; }
}
void test01(){using namespace A;cout << paramA << endl;cout << paramB << endl;funcA();funcB();//不会产生二义性,若当前局部变量有定义则使用,若无则到命名空间里面寻找int paramA = 30;cout << paramA << endl;
}
namespace B{int paramA = 20;int paramB = 30;void funcA(){ cout << "hello funcA" << endl; }void funcB(){ cout << "hello funcB" << endl; }
}
void test02(){using namespace A;using namespace B;//二义性产生,不知道调用A还是B的paramAcout << paramA << endl;
}
注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。
c中定义结构体变量需要加上struct关键字,c++不需要。 c中的结构体只能定义成员变量,不能定义成员
函数。c++即可以定义成员变量,也可以定义成员函数。
struct Student{string mName;int mAge;void setName(string name){ mName = name; }void setAge(int age){ mAge = age; }void showStudent(){cout << "Name:" << mName << " Age:" << mAge << endl;}
};
void test01(){Student student;//c++中定义结构体变量不需要加struct关键字student.setName("John");student.setAge(20);student.showStudent();
}
标准c++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。
这三个名字都是关键字。 bool类型只有两个值,true(1值),false(0值) ,bool类型本质上是1字节的整数(unsigned char),取值只有1和0。
给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)
void test()
{cout << sizeof(false) << endl; //为1,//bool类型占一个字节大小bool flag = true;flag = 10; //给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)
}
在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用 传递(pass-by-reference)。
引用可以作为一个已定义变量的别名。
引用的主要用途是用作函数的形参和返回值
。1、&别名
2、给哪个变量取别名 就定义该变量
3、从上往下整体替换!!【替换方式理解较为直观】
数据类型 &引用名=原变量名;
1、系统不会为引用 开辟空间
2、引用必须初始化
int a = 10;//需求:给变量a 取个别名叫b
//定义的时候 &修饰变量为引用 b就是a的别名(引用)
//系统不会为引用 开辟空间
int &b = a;//引用必须初始化//a和b代表同一空间内容
cout<<"a = "<
int arr[5]={10,20,30,40,50};
int n = sizeof(arr)/sizeof(arr[0]);int (&myArr)[5] = arr;
int i=0;
for(i=0;icout<
int num = 10;
int *p = #
int* &myP = p;cout<<"*p = "<<*p<
void fun01(void)
{cout<<"fun01"<,endl;
}
void (&myFun)(void) = fun01;myFun();//fun01
函数内部可以 通过 引用 操作外部变量
这种方法也叫按引用传递或传引用。(传值、传地址、传引用只是说法不同,其实都是传值。)
引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。
void swap01(int *p1, int *p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;}
void swap02(int &x, int &y)
{//形参赋值其实有如下赋值语句:int &x=a, int &y=bint tmp = x;x = y;y = tmp;
}
int main()
{int a = 10;int b = 20;cout<<"a = "<
引用的语法更清楚简单:
- 函数调用时传递的实参不必加“&”符
- 在被调函数中不必在参数前加“*”符引用作为其它变量的别名而存在,因此在一些场合可以代替指针。
- C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。
4) 传引用不必使用二级指针。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。void func1(int** p) // 传地址,实参是指针的地址,形参是二级指针。
{*p = new int(3); // p是二级指针,存放指针的地址。cout << "func1内存的地址是:" << *p << ",内存中的值是:" << **p << endl;
}void func2(int*& p) // 传引用,实参是指针,形参是指针的别名。
{p = new int(3); // p是指针的别名。cout << "func2内存的地址是:" << p << ",内存中的值是:" << *p << endl;
}int main()
{int* p = nullptr; // 存放在子函数中动态分配内存的地址。func1(&p); // 传地址,实参填指针p的地址。func2(p); // 传引用,实参填指针p。cout << "main 内存的地址是:" << p << ",内存中的值是:" << *p << endl;delete p;
}
函数的返回值被拷贝到一个临时位置(小就放在寄存器中或大就放在栈),然后调用者程序再使用这个值。
cout << sqrt(25);
//sqrt(25)的返回值5被拷贝到临时的位置,然后传递给cout。
如果返回的是一个结构体,同样是将把整个结构体拷贝到临时的位置。【解决方法是返回地址或引用,不会导致内存拷贝】
语法:
返回值的数据类型& 函数名(形参列表);
注意:
返回函数的引用形参【cout实现的原理】
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。const int &func2(int &ra) // 返回的是引用。将const用于引用的返回类型,这样变量就无法修改
{ra++;cout << "ra的地址是:" << &ra << ",ra=" << ra << endl;return ra;
}int main()
{int a = 3;const int& b = func2(a); // 返回的是引用。cout << " a的地址是:" << &a << ", a=" << a << endl;cout << " b的地址是:" << &b << ", b=" << b << endl;// func2(a) = 10; // 返回引有的函数是被引用的变量的别名。由于返回值是const,不能被修改// cout << " a的地址是:" << &a << ", a=" << a << endl;// cout << " b的地址是:" << &b << ", b=" << b << endl;
}
1、不要返回普通局部变量的引用
int& getData(void)
{int num = 10;//不要返回局部变量的引用,其本质是野指针,后果不可预知。return num;//返回num 函数调用的结果 就是num的别名
}
int main()
{//b就是num的别名int &b = getData();//b是getData()的别名,而getData()是num别名,所以间接可得b是num别名
}
2、返回值类型为引用 可以完成链式操作(调用)
struct Stu
{//结构体中可以定义成员函数//函数的成员是引用//函数的返回值是引用Stu& printStu(Stu &ob, int value){cout<Stu ob1;ob1.printStu(ob1, 100).printStu(ob1, 200).printStu(ob1, 300);//100 200 300
}
如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量。
什么时候将创建临时变量呢?
结论:如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。
将引用形参声明为const的理由有三个:
左值与非左值
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。void func1(int no, string str) // 传值。
{cout << "亲爱的" << no << "号:" << str << endl;
}void func2(const int* no,const string* str) // 传地址。
{cout << "亲爱的" << *no << "号:" << *str << endl;
}void func3(const int& no, const string& str) // 传引用。
{cout << "亲爱的" << no << "号:" << str << endl;
}int main()
{func1(8, "我是一只小小鸟。");func2(8, "我是一只小小鸟。");//传递地址即便加了const也不能转换func3('X', "我是一只小小鸟。");//有const,c风格的字符串会转化为形参string,字符常量会转化为形参int类型}
补充:
1、给常量取别名,不能通过常引用 修改 内容。
//int &a = 10;//err
const int &a = 10;//a就是10的别名
//a=100;//err,用const修饰后不能修改
cout<
2、常引用 作为函数的参数:防止函数内部修改外部的值。【主要】
//形参不加const则会导致函数内部可以修改函数外部的值
void printInt(const int &a)
{//a = 200;//errcout<<"a = "<int num = 100;printInt(num);//a = 100
}
传值、传地址和传引用的指导原则《C++ Primer Plus》
(没有为数组建立引用的说法)
。例如:对于基本类型,cin使用引用,因此可以使用cin>>a,而不是cin>>&a。
内联函数 必须在定义的时候 使用关键字inline修饰, 不能在声明的时候使用inline
//函數声明的时候 不要使用inline
int myAdd(int x, int y);
int main()
{cout<return x+y;
}
内联函数:在编译阶段 将内联函数中的函数体 替换函数调用处。避免函数调用时的开销。
宏函数和内联函数 都会在适当的位置 进行展开 避免函数调用开销。
宏函数在预处理阶段展开
内联函数在编译阶段展开
宏函数的参数没有类型,不能保证参数的完整性。
内联函数的参数有类型 能保证参数的完整性。
宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员
内联函数有作用域的限制,能作为命名空间、结构体、类的成员
在内联函数定义的时候加inline修饰
类中的成员函数 默认都是内联函数(不加inline 也是内联函数)
有时候 就算加上inline也不一定是内联函数(内联函数条件)
有时候不加inline修饰 也有可能是内联函数。
内不内联 由编译器决定。
能使名字方便使用,是任何程序设计语言的一个重要特征!
同样一个字在不同的场景下具有不同的含义。那么在c++中也有一种类似的现象出现,同一个函数名在不同场景下可以具有不同的含义。
函数重载 是c++的多态的特性(静态多态)。
函数重载:用同一个函数名 代表不同的函数功能。
注意:
同一作用域,函数的参数类型不同、个数不同、顺序不同 都可以重载。(返回值类型不能作为重载的条件)
void printFun(int a)
{cout<<"int"<cout<<"int char"<cout<<"char int"<cout<<"char"<printFun(10);//intprintFun(10, 'a');//int charprintFun('a', 10);//char intprintFun('a');//char
}
思考:为什么函数返回值不作为重载条件呢?
我们在编写程序过程中可以忽略他的返回值
。那么这个时候,一个函数为 void func(int x);另一个为int func(int x);当我们直接调用func(10),这个时候编译器就不确定调用那个数。所以在c++中禁止使用返回值作为重载的条件void func(){}
void func(int x){}
void func(int x,char y){}
以上三个函数在linux下生成的编译之后的函数名为
_Z4funcv //v 代表void,无参数
_Z4funci //i 代表参数为int类型
_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型
不同的编译器可能会产生不同的内部名,只是举例说明
c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
语法:返回值 函数名(数据类型 参数=值, 数据类型 参数=值,……);
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。void func(int bh,const string &name="西施", const string& message="我喜欢你。") // 向超女表白的函数。
{cout << "亲爱的"<func(3,"冰冰","我是一只傻傻鸟。"); //非左值转换,形参需要加constfunc(5);
}
void TestFunc01(int a = 10, int b = 20){cout << "a + b = " << a + b << endl;
}//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}int main(){//1.如果没有传参数,那么使用默认参数TestFunc01();//2. 如果传一个参数,那么第二个参数使用默认参数TestFunc01(100);//3. 如果传入两个参数,那么两个参数都使用我们传入的参数 TestFunc01(100, 200);return 0;
}
默认参数和函数重载同时出现 一定要注意二义性
void func(int x)
{cout<<"A:int x="<cout<<"B:int x="<func(10,20);//okfunc(10);//err 产生二义性
}
c++在声明函数时,可以设置占位参数。占位参数只有参数类型,而没有参数名。一般情况下,在函数体内部无法使用占位参数。
void TestFunc01(int a,int b,int){//函数内部无法使用占位参数cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值,但是无法使用
void TestFunc02(int a, int b, int = 20){//函数内部依旧无法使用占位参数cout << "a + b = " << a + b << endl;}
int main(){//错误调用,占位参数也是参数,必须传参数//TestFunc01(10,20);//正确调用TestFunc01(10,20,30);//正确调用TestFunc02(10,20);//正确调用TestFunc02(10, 20, 30);return 0;
}
什么时候用,在后面我们要讲的操作符重载的后置++要用到这个.
以下在Linux下测试:
c函数: void MyFunc(){} ,被编译成函数: MyFunc
c++函数: void MyFunc(){},被编译成函数: _Z6Myfuncv
通过这个测试,由于c++中需要支持函数重载,所以c和c++中对同一个函数经过编译后生成的函数名是
不相同的,这就导致了一个问题,如果在c++中调用一个使用c语言编写模块中的某个函数,那么c++是
根据c++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,c++中调用MyFunc
函数,在链接阶段会去找Z6Myfuncv,结果是没有找到的,因为这个MyFunc函数是c语言编写的,生成
的符号是MyFunc。 那么如果我想在c++调用c的函数怎么办? extern "C"的主要作用就是为了实现c++代
码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而 不是按c++的方式。
fun.h
#ifndef MYMODULE_H
#define MYMODULE_H
#include
#if __cplusplus
extern "C"{
#endifextern void func1();extern int func2(int a,int b);
#if __cplusplus}#endif
#endif
fun.c
#include#include"fun.h"
void func1(){printf("hello world!");
}
int func2(int a, int b){return a + b;
}
main.cpp
#include
#include "fun.h"using namespace std;
int main(){func1();cout << func2(10, 20) << endl;return 0;
}
类将具有共性的数据和方法封装在一起,加以权限区分
,用户只能通过公共方法 访问私有数据
。
类的权限分为:private(私有)、protected(保护)、public(公有)3种权限。
在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时, private和protected是同
等级的,外部不允许访问。用户在类的外部可以通过public的方法间接访问private和protected数据
在一个类体的定义中,private 和 public 可以出现多次。
结构体的成员缺省为public,类的成员缺省为private。
private的意义在于隐藏类的数据和实现,把需要向外暴露的成员声明为public。
类的关键字:class
#include using namespace std;//类Data 是一个类型
class Data
{//类中 默认为私有
private:int a;//不要给类中成员 初始化
protected://保护int b;
public://公共int c;//在类的内部 不存在 权限之分void showData(void){cout<//类实例化一个对象Data ob;//类外不能直接访问 类的私有和保护数据//cout<
设计一个类步骤:思考该类有哪些数据成员 操作这些数据的成员函数 数据位私有 成员函数为公有
成员函数 :在类中声明, 类外实现
class Data
{
private:int mA;
public://类中声明void setA(int a);int getA(void);
};
//类外实现,也属于类的内部,可以使用私有或保护的变量
//在qt中通过alt+enter可以自动写好类的外实现
void Data::setA(int a)
{mA = a;
}
int Data::getA()
{return mA;
}
通过Qt Creator为c++工程添加一个类(步骤如下)
注意:类定义在同文件data.h中,而data.cpp是用来实现类的成员函数
data.h
#ifndef DATA_H
#define DATA_H
class Data
{
private:int mA;
public:void setA(int a);在qt中通过alt+enter可以自动写好类的外实现int getA(void);
};
#endif // DATA_H
data.cpp
#include "data.h"
void Data::setA(int a)
{mA=a;
}
int Data::getA()
{return mA;
}
main.cpp
#include
#include "data.h"
using namespace std;int main(int argc, char *argv[])
{Data ob;ob.setA(100);cout<<"mA = "<
类的成员可以是任意数据类型(类中使用枚举,可读性更好,作用域是类,不是整个程序)。
为了区分类的成员变量和成员函数的形参,把成员变量名加m_前缀或_后缀,如m_name或name_。
当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++为了给我们提供这种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用
,完成对象初始化和对象清理工
作。 无论你是否喜欢,对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操
作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类 就应该顺便提供初始化函数
。
类实例化对象的时候, 系统自动调用构造函数 ,完成对象的初始化。
如果用户不提供构造函数 编译器 会自动添加一个默认的构造函数(空函数)
构造函数名 和 类名相同,没有返回值类型(连void都不可以),可以有参数(可以重载),权限为 public
先给对象开辟空间(实例化) 然后调用构造函数(初始化)。
class Data
{
public:int mA;
public://无参构造函数Data(){mA=0;cout<<"无参构造函数"<mA=a;cout<<"有参构造函数 mA="<//隐式调用无参构造函数(推荐)Data ob1;//显示调用无参构造函数Data ob2 = Data();//隐式调用有参构造函数(推荐)Data ob3(10);//显示调用有参构造函数Data ob4 = Data(10);//匿名对象(无参) 当前语句技术 立即释放Data();Data(20);//构造函数隐式转换(类中只有一个数据成员)Data ob5 = 100;
}
如果用户不提供任何构造函数 编译器默认提供一个空的无参构造。
如果用户定义了构造函数(不管是有参、无参),编译器不再提供默认构造函数。
例如:自己写了有参构造后系统不会提供无参构造,如果再使用无参构造则会报错
#include using namespace std;
class Data
{
public:int mA;
public:
#if 0//无参构造函数Data(){mA=0;cout<<"无参构造函数"<mA=a;cout<<"有参构造函数 mA="<int main(int argc, char *argv[])
{Data ob;//报错cout<
函数名和类名称相同,在函数名前加~,没有返回值类型,没有函数形参。(不能被重载)
当对象生命周期结束的时候 系统自动调用析构函数。
先调用析构函数 再释放对象的空间。
class Data1
{
public:int mA;
public://无参构造函数Data1(){mA=0;cout<<"无参构造函数"<mA=a;cout<<"有参构造函数 mA="<cout<<"析构函数 mA="<
ob2和ob4是栈的结构,所以析构时ob4先出栈
一般情况下,系统默认的析构函数就足够。但是如果一个类有指针成员,这个类必须 ,写析构函数,释放 ,指针成员所指向空间。
系统默认回收对象本身的空间,不会回收对象中的指针指向的空间
#include
class Data2
{
public:char *name;
public:Data2(){name=NULL;}Data2(char *str){//指针成员指向堆空间,需要写析构函数清理堆空间name = new char[strlen(str)+1];strcpy(name, str);cout<<"有参构造"<if(name != NULL)delete [] name;cout<<"析构函数"<Data2 ob("hello world");cout<
创建对象的时候,如果重载了构造函数,编译器根据实参匹配相应的构造函数。没有参数的构造函数也叫默认构造函数。
创建对象的时候(如果没有构造函数、构造函数没有参数、构造函数的参数都有默认参数)不要在对象名后面加空的圆括号,编译器误认为是声明函数,在使用对象里面的变量时会报错
在构造函数名后面加括号和参数不是调用构造函数,是创建匿名对象
。
以下两行代码有本质的区别:
//只构造和析构一次
CGirl girl = CGirl("西施"20); // 显式创建对象。
//构造和析构两次
CGirl girl; // 创建对象。
girl = CGirl("西施"20); // 创建匿名对象,然后给现有的对象赋值。
// CGirl *girl=new CGirl; // 创建超女对象,不设置任何初始值。
// CGirl *girl=new CGirl("西施"); // 创建超女对象,为成员姓名设置初始值。
CGirl *girl=new CGirl("西施",8); // 创建超女对象,为成员姓名和年龄设置初始值。girl->show(); // 显示超女的自我介绍。
delete girl;
CGirl girl = {"西施"20};
CGirl girl {"西施"20};
CGirl* girl = new CGirl{ "西施"20 };
拷贝构造:本质是构造函数
拷贝构造的调用时机:旧对象 给新对象 初始化 才会调用拷贝构造。
它的功能是把已存在对象的成员变量赋值给新对象的成员变量。
用一个已存在的对象创建新的对象语法:
类名 新对象名(已存在的对象名);
类名 新对象名=已存在的对象名;
拷贝构造函数的语法:
类名(const 类名& 对象名){......}
#include
using namespace std;
class Data
{
public:int mA;
public:Data(){cout<<"无参构造"<mA = a;cout<<"有参构造 mA="<//一旦实现了 拷贝构造函数 必须完成赋值操作mA = ob.mA;cout<<"拷贝构造函数"<cout<<"析构函数mA="<Data ob1(10);//旧对象给新对象初始化 就会调用拷贝构造函数Data ob2 = ob1;cout<<"ob2.mA ="<
如果用户不提供拷贝构造 编译器会自动提供一个默认的拷贝构造(完成赋值动作–浅拷贝)
人为写拷贝构造函数是为了完成深拷贝
拷贝构造函数可以重载,可以有默认参数。
类名(......,const 类名& 对象名,......){......}
如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。
拷贝构造是特殊的有参构造,只是参数是类的常引用
1、旧对象给新对象初始化 调用拷贝构造
Data ob1(10);
Data ob2 = ob1;//调用拷贝构造
2、给对象取别名 不会调用拷贝构造
Data ob1(10);
Data &ob2 = ob1;//不会调用拷贝构造
3、普通对象作为函数参数 调用函数时 会发生拷贝构造
void func(Data ob)//Data ob=ob1
{
}int main()
{Data ob1(100);//有参构造func(ob1);//拷贝构造
}
4、函数返回值普通对象
Visual Studio会发生拷贝构造
函数返回值特点:
Qtcreater,linux不会发生
优化后该空间直接交给ob2析构
默认的拷贝构造 都是浅拷贝。
如果类中没有指针成员, 不用实现拷贝构造和析构函数。
如果类中有指针成员,且指向堆区空间, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作。
#include
#includeusing namespace std;
class Data5
{
public:char* name;
public:Data5(){name = NULL;}Data5(char* str){name = new char[strlen(str) + 1];strcpy(name, str);cout << "有参构造 name=" << name << endl;}Data5(const Data5& ob)//深拷贝{//为对象的指针成员申请独立的空间name = new char[strlen(ob.name) + 1];strcpy(name, ob.name);cout << "拷贝构造函数" << endl;}~Data5(){cout << "析构函数name = " << name << endl;if (name != NULL){delete [] name;name = NULL;}}
};
void test05()
{Data5 ob1((char *)"hello world\n");Data5 ob2 = ob1;
}
Data5 ob2 = ob1;实例化默认会发送浅拷贝,会导致在析构的时候会报错
构造函数的执行可以分成两个阶段:初始化阶段和计算阶段(初始化阶段先于计算阶段)。
构造函数除了参数列表和函数体之外,还可以有初始化列表。
初始化列表的语法:
类名(形参列表):成员一(值一), 成员二(值二),..., 成员n(值n)
{......}
注意:
1)如果成员已经在初始化列表中,则不应该在构造函数中再次赋值。如果在构造函数中赋值会覆盖掉
2)初始化列表的括号中可以是具体的值,也可以是构造函数的形参名,还可以是表达式。【构造函数形参可以取代掉】
可以是构造函数的形参名
可以是表达式
3)初始化列表与赋值有本质的区别,如果成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先创建成员类的对象(将调用成员类的普通构造函数),然后再赋值。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class CBoy // 男朋友类。
{
public:string m_xm; // 男朋友的姓名。CBoy() // 没有参数的普通构造函数,默认构造函数。 { m_xm.clear(); cout << "调用了CBoy()构造函数。\n"; }CBoy(string xm) // 有一个参数的普通构造函数。 { m_xm = xm; cout << "调用了CBoy(string xm)构造函数。\n"; }CBoy(const CBoy& bb) // 默认拷贝构造函数。 { m_xm = bb.m_xm; cout << "调用了CBoy(const CBoy &bb)拷贝构造函数。\n"; }
};class CGirl // 超女类CGirl。
{
public:string m_name; // 姓名属性。const int m_age; // 年龄属性。CBoy m_boy; // 男朋友的信息。//采用赋值的方法调用的是成员对象的普通构造函数CGirl(string name, int age, CBoy boy) :m_name(name), m_age(age), // 三个参数的普通构造函数。{m_boy.m_xm = boy.m_xm;cout << "调用了CGirl(name,age,boy)构造函数。\n";}//形参为引用则不会调用成员对象的拷贝构造CGirl(string name, int age, CBoy& boy) :m_name(name), m_age(age), // 三个参数的普通构造函数。{m_boy.m_xm = boy.m_xm;cout << "调用了CGirl(name,age,boy)构造函数。\n";}//使用初始化列表会调用成员对象的拷贝构造CGirl(string name, int age, CBoy& boy) :m_name(name), m_age(age),m_boy(boy) // 三个参数的普通构造函数。{cout << "调用了CGirl(name,age,boy)构造函数。\n";}// 超女自我介绍的方法,显示姓名、年龄、男朋友。void show() { cout << "姓名:" << m_name << ",年龄:" << m_age << ",男朋友:" << m_boy.m_xm << endl; }
};int main()
{CBoy boy("哈哈");CGirl g1("冰冰",18,boy);g1.show();
}
如果成员是类,初始化列表对性能略有提升。
【因为有初始化列表,对象的初始化和赋初始值是一步操作,否则是2步操作】#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class CBoy // 男朋友类。
{
public:string m_xm; // 男朋友的姓名。CBoy() // 没有参数的普通构造函数,默认构造函数。 { m_xm.clear(); cout << "调用了CBoy()构造函数。\n"; }CBoy(string xm) // 有一个参数的普通构造函数。 { m_xm = xm; cout << "调用了CBoy(string xm)构造函数。\n"; }CBoy(const CBoy& bb) // 默认拷贝构造函数。 { m_xm = bb.m_xm; cout << "调用了CBoy(const CBoy &bb)拷贝构造函数。\n"; }
};class CGirl // 超女类CGirl。
{
public:string m_name; // 姓名属性。const int m_age; // 年龄属性为常量,则需要用初始化列表初始化CBoy& m_boy; // 男朋友的信息。对象是引用则需要用初始化列表初始化CGirl(string name, int age, CBoy& boy) :m_name(name), m_age(age),m_boy(boy) // 三个参数的普通构造函数。{cout << "调用了CGirl(name,age,boy)构造函数。\n";}// 超女自我介绍的方法,显示姓名、年龄、男朋友。void show() { cout << "姓名:" << m_name << ",年龄:" << m_age << ",男朋友:" << m_boy.m_xm << endl; }
};int main()
{CBoy boy("子都");CGirl g1("冰冰",18,boy);g1.show();
}
在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做成员对象。
先调用对象成员的构造函数,再调用本身的构造函数。 析构函数和构造函数调用顺序相反,先自身,后成员
上述中,类会自动调用对象成员的无参构造。【默认】
类想调用对象成员 有参构造 必须使用初始化列表。
class A
{
public:int mA;
public:A(){mA = 0;cout<<"A的无参构造"<mA = a;cout<<"A的有参构造"<cout<<"A的析构函数"<
public:int mB;A ob;//成员对象
public:B(){cout<<"B类的无参构造"<mB = b;cout<<"B类的有参构造"<cout<<"B的析构函数"<B ob1(10,20);cout<<"mA ="<
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
注意explicit用于修饰构造函数,防止隐式转化。 是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
class MyString{
public:explicit MyString(int n){cout << "MyString(int n)!" << endl;}MyString(const char* str){cout << "MyString(const char* str)" << endl;}
};
int main(){//给字符串赋值?还是初始化?//MyString str1 = 1;会发送隐式转换MyString str2(10);//寓意非常明确,给字符串赋值//MyString str3 = "abcd";会发送隐式转换MyString str4("abcd");return 0;
}
对象数组:本质是数组 数组的每个元素是对象。
class A{
public:int mA;
public:A(){mA = 0;cout<<"A的无参构造 mA="<mA = a;cout<<"A的有参构造mA="<cout<<"A的析构函数 mA = "<//对象数组 每个元素都会自动调用构造和析构函数//该调用方式中 ,对象数组不初始化 每个元素 调用无参构造 A arr1[5];//该调用方式中 ,对象数组的初始化 必须显示使用有参构造 逐个元素初始化A arr2[5]={A(10),A(20),A(30),A(40),A(50) };int n =sizeof(arr2)/sizeof(arr2[0]);int i=0;for(i=0;icout<
析构采用栈的思想
当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小再好不过。 所以动态的意思意味着不确定性。 为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配(dynamicmemory allocation),函数malloc和free可以在运行时从堆中分配存储单元。 然而这些函数在c++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。
当创建一个c++对象时会发生两件事:
class Person{
public:Person(){mAge = 20;pName = (char*)malloc(strlen("john")+1);strcpy(pName, "john");}void Init(){mAge = 20;pName = (char*)malloc(strlen("john")+1);strcpy(pName, "john");}void Clean(){if (pName != NULL){free(pName);}}
public:int mAge;char* pName;
};
int main(){//分配内存Person* person = (Person*)malloc(sizeof(Person));if(person == NULL){return 0;}//调用初始化函数person->Init();//清理对象person->Clean();//释放person对象free(person);return 0;
}
问题:
c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,c++中我们推荐使用运算符new 和delete。
C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
Person* person = new Person;
New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查
。这样在堆创建一个对象和在栈里创建对象一样简单
new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。
class Person{
public:Person(){cout << "无参构造函数!" << endl;pName = new char[strlen("undefined") + 1];strcpy(pName, "undefined");mAge = 0;}Person(char* name, int age){cout << "有参构造函数!" << endl;pName = new char[strlen(name) + 1];strcpy(pName, name);mAge = age;}void ShowPerson(){cout << "Name:" << pName << " Age:" << mAge << endl;}~Person(){cout << "析构函数!" << endl;if (pName != NULL){delete [] pName;pName = NULL;}}
public:char* pName;int mAge;
};
int main(){Person* person1 = new Person;Person* person2 = new Person("John",33);person1->ShowPerson();person2->ShowPerson();delete person1;delete person2;
}
当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化,必须提供一个默认的构造函数
class Person{
public:Person(){pName = NULL;mAge = 0;}Person(char* name, int age){pName = new char[strlen(name)+1];strcpy(pName, name);mAge = age;}~Person(){if (pName != NULL){delete [] pName;}}
public:char* pName;int mAge;
};
void test(){//栈聚合初始化局部数组Person person[] = { Person("john", 20), Person("Smith", 22) };cout << person[1].pName << endl;//创建堆上对象数组必须提供构造函数Person* workers = new Person[20];delete [] workers;//申请的时候有中括号,取消也需要中括号
}
static修饰的静态成员 属于类而不是对象。(所有对象 共享 一份 静态成员数据)。
static修饰的成员 定义类的时候 必须分配空间。
static修饰的静态成员数据 必须类中定义 类外初始化。【因为静态成员数据是先于对象存在的, 静态成员变量不会在创建对象的时候初始化】
class Data
{
public:int a;//普通成员数据//类中定义static int b;//静态成员数据
};
//类外初始化
int Data::b=100;//不用加static
void test01()
{//静态成员数据 通过类名称直接访问(属于类),在还没定义对象之前cout<
静态成员函数 是属于类 而不是对象(所有对象 共享)
class Data
{static void func()//静态成员函数{}
}
当静态数据为私有的时候,需要通过静态函数获取私有数据
静态成员函数 可以直接通过类名称访问
静态成员函数内 只能操作静态成员数据
。因为其他成员数据属于对象,而此时对象还没有创建,只存在类这个概念。
在非静态成员函数中,可以访问静态成员。
静态成员函数中没有this指针。因为没有对象
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案
只能实例化一个对象:通过构造函数私有化,外界无法调用构造函数去创建对象,只能在类中实例化
#include
using namespace std;
class SingleTon//单例模式
{
//构造私有化 防止实例化其他对象
private:SingleTon(){count=0;cout<<"构造"<count=0;}~SingleTon(){cout<<"析够"<return p;}//用户自定义 任务函数void printString(char *str){count++;cout<<"当前第"<//获取单例的地址SingleTon *p1 =SingleTon::getSingleTon();p1->printString("离职证明1");p1->printString("学历证明1");p1->printString("学位证明1");p1->printString("身份证明1");SingleTon *p2 =SingleTon::getSingleTon();p2->printString("离职证明2");p2->printString("学历证明2");p2->printString("学位证明2");p2->printString("身份证明2");return 0;
}
c++实现了“封装”,“数据”和“处理数据的操作(函数)”是分开存储的。 c++中的非静态数据成员直接内含在 类对象中,成员函数虽然内含在class声明之内,却不出现在对象中。 每一个非内联成员函数只会诞生一份函数实例。
sizeof(Data1)的大小只是 a 和 b所占空间大小(类的对象所占空间大小)。
class MyClass01{
public:int mA;
};
class MyClass02{
public:int mA;static int mB;
};
class MyClass03{
public: void printMyClass(){cout << "hello world!" << endl;}
public:int mA;static int mB;
};
class MyClass04{
public:void printMyClass(){cout << "hello world!" << endl;
}static void ShowMyClass(){cout << "hello world!" << endl;}
public:int mA;static int mB;
};
int main(){MyClass01 mclass01;MyClass02 mclass02;MyClass03 mclass03;MyClass04 mclass04;cout << "MyClass01:" << sizeof(mclass01) << endl; //4//静态数据成员并不保存在类对象中cout << "MyClass02:" << sizeof(mclass02) << endl; //4//非静态成员函数不保存在类对象中cout << "MyClass03:" << sizeof(mclass03) << endl; //4//静态成员函数也不保存在类对象中cout << "MyClass04:" << sizeof(mclass04) << endl; //4return 0;
}
通过上面的案例,我们可以的得出:C++类对象中的变量和函数是分开存储。
C++类中有两种数据成员:nonstatic、static,三种函数成员:nonstatic、static、virtual。
对象指针表负责管理地址【类对象中变量和函数的地址是连续的,但是静态成员变量和成员函数在静态存储区中,不是在对象中】
如果在成员函数的括号后面使用const,那么将不能通过this指针修改成员变量。
通过上例我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码 。那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
。
成员函数通过this指针即可知道操作的是那个对象的数据。·This指针是一种隐含指针·,它隐含于每个类的非静态成员函数中。This指针无需定义,直接使用即可。
注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量
【因为和对象有关,而静态成员变量和静态成员函数都与对象无关】
构造函数也属于成员函数(非静态),此时this指向调用的对象,this保存的是调用对象的地址
构造函数和析构函数也有this
cout方法原理就是这个
用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量, 当成员变量类型符前用mutable修饰时例外
class Data
{
public:int a;int b;mutable int c;
public:Data(int a, int b,int c){this->a = a;this->b = b;this->c = c;}//const 修饰成员函数为只读(该成员函数不允许对 成员数据 赋值) mutable修饰的成员除外//该函数不可以修改普通成员数据void showData(void) const{//a = 100;//errc=100;cout<Data ob1(10,20,30);ob1.showData();
}
在类的成员函数后面加const关键字,表示在成员函数中保证不会修改调用对象的成员变量。
注意:
这里出现了令人纠结的三个问题:
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class CGirl // 超女类CGirl。
{
public:mutable string m_name; // 姓名属性。int m_age; // 年龄属性。// 两个参数的普通构造函数。CGirl(const string &name, int age) { m_name = name; m_age = age; cout << "调用了CGirl(name,age)构造函数。\n"; }// 超女自我介绍的方法,显示姓名、年龄。void show1() const{ m_name="西施show1";cout << "姓名:" << m_name << ",年龄:" << m_age << endl; show2();//const成员函数不能调用非const成员函数}void show2() const{m_name = "西施show2";cout << "姓名:" << m_name << ",年龄:" << m_age << endl;}void show3() {m_name = "西施show3";cout << "姓名:" << m_name << ",年龄:" << m_age << endl;}void show4() {m_name = "西施show4";cout << "姓名:" << m_name << ",年龄:" << m_age << endl;}
};int main()
{const CGirl g1("冰冰",18);g1.show1();//const对象只能调用const修饰的成员函数
}
类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元函数,友元函数是一种特权函数,c++允许这个特权函数访问私有成员。
这一点从现实生活中也可以很好的理解: 比如你的家,有客厅,有你的卧室,那么你的客厅是Public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。 程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。
使用friend关键字声明友元。
friend关键字只出现在声明处
,一个函数或者类
作为了另一个类的友元 那么这个函数或类 就可以直接访问 另一个类的私有数据。
友元 重要用在运算符重载上。
#include
using namespace std;
class Room
{friend void visiting01(Room &room);
private:string bedRoom;//卧室
public:string setingRoom;//客厅
public:Room(string bedRoom, string setingRoom){this->bedRoom = bedRoom;this->setingRoom = setingRoom;}
};
//普通全局函数
void visiting01(Room &room)
{cout<<"访问了"<Room room("卧室","客厅");visiting01(room);return 0;
}
注意顺序:类的友元写在前面 ,同时他的成员函数要在所有类的最下方外部实现
class Room;//向前声明 只能说明类名称
class goodGay
{
public:void visiting01(Room &room);void visiting02(Room &room);
};class Room
{friend void goodGay::visiting02(Room &room);
private:string bedRoom;//卧室
public:string setingRoom;//客厅
public:Room(string bedRoom, string setingRoom){this->bedRoom = bedRoom;this->setingRoom = setingRoom;}
};
void goodGay::visiting01(Room &room)
{cout<<"访问了"<cout<<"好基友张三访问了"<Room room("卧室","客厅");goodGay ob;ob.visiting01(room);ob.visiting02(room);return 0;
}
这个类的所有成员函数 都可以访问另一个类的私有数据.
class Room;//向前声明 只能说明类名称
class goodGay
{
public:void visiting01(Room &room);void visiting02(Room &room);
};
class Room
{friend class goodGay;
private:string bedRoom;//卧室
public:string setingRoom;//客厅
public:Room(string bedRoom, string setingRoom)
{this->bedRoom = bedRoom;this->setingRoom = setingRoom;
}
};
void goodGay::visiting01(Room &room)
{cout<<"访问了"<cout<<"好基友访问了"<Room room("卧室","客厅");goodGay ob;ob.visiting01(room);ob.visiting02(room);return 0;
}
1.友元关系不能被继承。
2.友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。
3.友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友
请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量操作的方法,频道操作的方
法。由于电视机只能逐一调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能,
再增加根据输入调台功能。
提示:遥控器可作为电视机类的友元类
#include
using namespace std;
class TV;
//遥控器的类作为TV的友元
class Remote
{
private:TV *p;
public:Remote(TV *p);void offOrOn(void);void upVolume(void);void downVolume(void);void upChannel(void);void downChannel(void);void showTv(void);void setChannel(int channel);
};
class TV
{friend class Remote;enum{OFF,ON};enum{minVol, maxVol=10};enum{minChan, maxChan=25};
private:int state;int volume;int channel;
public:TV(){state = OFF;volume = minVol;channel = minChan;}void offOrOn(void);void upVolume(void);void downVolume(void);void upChannel(void);void downChannel(void);void showTv(void);
};
int main(int argc, char *argv[])
{TV tv;Remote re(&tv);re.offOrOn();re.upVolume();re.upVolume();re.setChannel(10);re.showTv();return 0;
}
void TV::offOrOn()
{state = (state==OFF?ON:OFF);
}
void TV::upVolume()
{if(volume == maxVol){cout<<"音量已经最大"<if(volume ==minVol){cout<<"音量已经最小"<if(channel == maxChan){cout<<"频道已经最大"<if(channel == minChan){cout<<"频道已经最小"<cout<<"当前电视机的状态:"<<(state==OFF?"关":"开")<this->p = p;
}
void Remote::offOrOn()
{p->offOrOn();
}
void Remote::upVolume()
{p->upVolume();
}
void Remote::downVolume()
{p->downVolume();}
void Remote::upChannel()
{p->upChannel();
}
void Remote::downChannel()
{p->downChannel();
}
void Remote::showTv()
{p->showTv();
}
void Remote::setChannel(int channel)
{if(channel>=TV::minChan && channel<=TV::maxChan){p->channel = channel;}else{cout<<"频道"<
视频网址
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
语法: 定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。
思路:
#include
using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person &ob);
private:int num;string name;float score;
public:Person(){}//初始化列表Person(int num,string name,float score):num(num),name(name),score(score){}
};
//全局函数重载operator<<
//完成链式操作
ostream& operator<<(ostream &out, Person &ob)
{out<Person lucy(100,"lucy", 99.8f);Person bob(101,"bob", 99.8f);cout<
如果使用全局函数 重载运算符 必须将全局函数设置成友元。
#include
#include using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person &ob);friend istream& operator>>(istream &in, Person &ob);private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}
};
//全局函数重载operator<<
ostream& operator<<(ostream &out, Person &ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
int main(int argc, char *argv[])
{Person lucy;Person bob;cin>>lucy>>bob;cout<
#include
#include using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person ob);friend istream& operator>>(istream &in, Person &ob);friend Person operator+(Person &ob1, Person &ob2);
private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}
};
//全局函数重载operator<<,参数二不采用引用
ostream& operator<<(ostream &out, Person ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
//全局函数重载+运算符
Person operator+(Person &ob1, Person &ob2)
{Person tmp;tmp.num = ob1.num+ob2.num;tmp.name = ob1.name+ob2.name;//string类型可以直接拼接tmp.score = ob1.score+ob2.score;return tmp;
}
int main(int argc, char *argv[])
{Person lucy(100,"lucy", 88.8);Person bob(101,"bob",99.9);cout<
#include
#include using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person ob);friend istream& operator>>(istream &in, Person &ob);
private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}//成员函数重载+运算符,不用设置右元Person operator+(Person &ob){//默认传递参数thisPerson tmp;tmp.num = num+ob.num;tmp.name = name+ob.name;tmp.score = score+ob.score;return tmp;}
};
//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
int main(int argc, char *argv[])
{Person lucy(100,"lucy", 88.8);Person bob(101,"bob",99.9);cout< lucy.operator+(bob)//cout<
#include
#include using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person ob);friend istream& operator>>(istream &in, Person &ob);
private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}//成员函数重载+运算符Person operator+(Person &ob){Person tmp;tmp.num = num+ob.num;tmp.name = name+ob.name;tmp.score = score+ob.score;return tmp;}//成员函数重载==运算符bool operator==(Person &ob){if(num==ob.num && name==ob.name && score==ob.score)return true;return false;}
};
//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
int main(int argc, char *argv[])
{Person lucy(100,"lucy", 88.8);Person bob(100,"lucy",88.8);if(lucy == bob){cout<<"相等"<cout<<"不相等"<
重载的++和–运算符有点让人不知所措,因为我们总是希望能根据它们出现在所作用对象的前面还是后面来调用不同的函数。解决办法很简单,例如当编译器看到++a(前置++),它就调用operator++(a),当编译器看到a++(后置++),它就会去调用operator++(a,int).//利用缺省参数
++a(前置++),它就调用operator++(a),
a++(后置++),它就会去调用operator++(a,int)
–a(前置–),它就调用operator–(a),
a–(后置–),它就会去调用operator–(a,int)
案例1:重载后置++
类名称 operator++(int)
{//先保存 旧的值old//自增++return old;//返回旧值
}
#include
include
#using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person ob);friend istream& operator>>(istream &in, Person &ob);
private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}//成员函数重载+运算符Person operator+(Person &ob){Person tmp;tmp.num = num+ob.num;tmp.name = name+ob.name;tmp.score = score+ob.score;return tmp;}//成员函数重载==运算符bool operator==(Person &ob){if(num==ob.num && name==ob.name && score==ob.score)return true;return false;}//重载后置++,默认传递参数this,谁调用this指向谁,存储的是地址值,要解引用得到对象Person operator++(int){//保存旧值Person old = *this;//++this->num++;this->name= this->name+this->name;this->score++;//返回值旧值,由于是局部对象,不能返回引用return old;}
};
//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
int main(int argc, char *argv[])
{Person lucy(100,"lucy", 88.8);Person bob;bob = lucy++;cout<
案例2:重载前置++
类名称 operator++()
{//自增++return *this;
}
#include
#include using namespace std;
class Person
{friend ostream& operator<<(ostream &out, Person ob);friend istream& operator>>(istream &in, Person &ob);
private:int num;string name;float score;
public:Person(){}Person(int num,string name,float score):num(num),name(name),score(score){}//成员函数重载+运算符Person operator+(Person &ob){Person tmp;tmp.num = num+ob.num;tmp.name = name+ob.name;tmp.score = score+ob.score;return tmp;}//成员函数重载==运算符bool operator==(Person &ob){if(num==ob.num && name==ob.name && score==ob.score)return true;return false;}//重载后置++Person operator++(int){//保存旧值Person old = *this;//++this->num++;this->name= this->name+this->name;this->score++;//返回值旧值return old;}//重载前置++Person operator++(){//++this->num++;this->name= this->name+this->name;this->score++;return *this;}
};//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{out<>
istream& operator>>(istream &in, Person &ob)
{in>>ob.num>>ob.name>>ob.score;return in;
}
int main(int argc, char *argv[])
{Person lucy(100,"lucy", 88.8);Person bob;bob = ++lucy;cout<
#include
#include
#include
using namespace std;
class MyString
{friend ostream& operator<<(ostream &out, MyString ob);friend istream& operator>>(istream &in, MyString &ob);
private:char *str;int size;
public:
MyString();
MyString(char *str);
MyString(const MyString &ob);
~MyString();
int getSize();
//成员函数重载[]运算符
char& operator[](int pos);
//重载+运算符
MyString operator+(MyString &ob);
MyString operator+(char *str);
//重载赋值运算符(只有指针成员operator=必须深拷贝)MyString& operator=(MyString ob);
MyString& operator=(char *str);
//重载关系运算符>
bool operator>(MyString ob);
bool operator>(char *str);
};
//全局函数重载<<运算符
ostream& operator<<(ostream &out, MyString ob)
{
out<>(istream &in, MyString &ob)
{
char buf[128]="";
cin>>buf;
//判断ob.str是否为NULL
if(ob.str != NULL)
{
delete [] ob.str;
ob.str=NULL;
}
ob.size = strlen(buf);
ob.str = new char[ob.size+1];
memset(ob.str, 0, ob.size+1);
strcpy(ob.str, buf);
return in;
}
int main(int argc, char *argv[])
{
MyString str1="hello";
str1[1]='H';
cout<>str2;
cout< str6)
{
cout<<"大于"<
cout<<"不大于"<
str=NULL;
size=0;
}
MyString::MyString(char *str)
{
this->str = new char[strlen(str)+1];
memset(this->str,0,strlen(str)+1);
size = strlen(str);
strcpy(this->str,str);
}
MyString::MyString(const MyString &ob)
{
size = ob.size;
str = new char[size+1];
memset(str,0,size+1);
strcpy(str,ob.str);
}
MyString::~MyString()
{
if(str != NULL)
{
delete [] str;
str=NULL;
}
}
int MyString::getSize()
{
return size;
}
char& MyString::operator[](int pos)
{
if(pos<0 || pos>=size)
{
cout<<"下标越界"<
MyString tmp;
tmp.size = this->size+ob.size;
tmp.str = new char[tmp.size +1];
memset(tmp.str, 0, tmp.size+1);
strcpy(tmp.str, this->str);
strcat(tmp.str, ob.str);
return tmp;
}
MyString MyString::operator+(char *str)
{
MyString tmp;
tmp.size = this->size+strlen(str);
tmp.str = new char[tmp.size +1];
memset(tmp.str, 0, tmp.size+1);
strcpy(tmp.str, this->str);
strcat(tmp.str, str);
return tmp;
}
MyString &MyString::operator=(MyString ob)
{
//str5=str4
if(str != NULL)
{
delete [] str;
str=NULL;
}
size = ob.size;
str = new char[size+1];
memset(str, 0,size+1);
strcpy(str, ob.str);
return *this;
}
MyString &MyString::operator=(char *str)
{
//str5=str4
if(this->str != NULL)
{
delete [] this->str;
this->str=NULL;
}
size = strlen(str);
this->str = new char[size+1];
memset(this->str, 0,size+1);
strcpy(this->str, str);return *this;
}
bool MyString::operator>(MyString ob)
{
if(str==NULL || ob.str == NULL)
{
exit(-1);
}
if(strcmp(str, ob.str) > 0)
return true;
return false;
}
bool MyString::operator>(char *str)
{
if(this->str==NULL || str == NULL)
{
exit(-1);
}
if(strcmp(this->str, str) > 0)
return true;
return false;
}
重载()运算符 一般用于 为算法 提供策略。
当对象和()结合 会触发 重载函数调用运算符
#include
using namespace std;
//仿函数class Print
{
public://重载函数调用运算符void operator()(char *str){cout<Print ob;//对象和()结合触发operator()调用ob("hello Print");//Print()为匿名对象,类名称是匿名对象也可以与小括号结合Print()("hello print");return 0;
}
智能指针:解决 堆区空间的对象 释放问题
忘记析构
创建智能指针对类进行包裹
#include
using namespace std;
class Data
{
public:Data(){cout<<"Data的无参构造"<cout<<"Data的析够"<cout<<"Data的func函数"<
private:Data *p;
public:SmartPointer(){}SmartPointer(Data *p){this->p = p;}//~SmartPointer(){delete p;}// 重载->运算符Data* operator->(){return p;}Data& operator*(){return *p;}
};
int main(int argc, char *argv[])
{
//通过智能指针创建对象SmartPointer sp(new Data);//sp.operator ->()->func();//局部对象sp调用结束后会析构sp->func();//Date *p = new Date;//p->func();//delete p;(*sp).func();//不取*号就是地址,取信号就是对象,用不同的运算符重载return 0;
}
不能重载operator&& 和 operator|| 的原因是,无法在这两种情况下实现内置操作符的完整语义。说得
更具体一些,内置版本版本特殊之处在于:内置版本的&&和||首先计算左边的表达式,如果这完全能够
决定结果,就无需计算右边的表达式了–而且能够保证不需要。我们都已经习惯这种方便的特性了。 我
们说操作符重载其实是另一种形式的函数调用而已,对于函数调用总是在函数执行之前对所有参
数进行求值。
class Complex{
public:Complex(int flag){this->flag = flag;}Complex& operator+=(Complex& complex){this->flag = this->flag + complex.flag;return *this;}bool operator&&(Complex& complex){return this->flag && complex.flag;}
public:int flag;
};
int main(){Complex complex1(0); //flag 0Complex complex2(1); //flag 1//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2的值, complex1.a = 1// 1 && 1//complex1.operator&&(complex1.operator+=(complex2))if (complex1 && (complex1 += complex2)){//complex1.operator+=(complex2)cout << "真!" << endl;}else{cout << "假!" << endl;return 0;
}
提高代码重用,提高开发效率。
继承可以理解为一个类从另一个类获取成员变量和成员函数的过程。
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不
仅拥有旧类的成员,还拥有新定义的成员。 一个B类继承于A类,或称从类A派生类B。这样的话,类A成
为基类(父类), 类B成为派生类(子类)。 派生类中的成员,包含两大部分: 一类是从基类继承过来
的,一类是自己增加的成员。 从基类继承过过来的表现其共性,而新增的成员体现了其个性。
class 父类{};
class 子类:继承方式 父类名
{
//新增子类数据
};
继承方式:private protected public(推荐)
所有父类私有在子类中不可访问,公共继承 保持不变,保护继承变保护,私有继承变私有。
class Base
{
private:int a;
protected:int b;
public:int c;
};
class Son:public Base
{//子类中 能访问 protected b public cpublic:void func(void){cout<Son ob;//cout<
如果不考虑继承关系,protected成员和private成员一样,类外不能访问。但是,当存在继承关系时,protected和private就不一样了。基类中的protected成员可以在派生类中访问,而基类中的 private成员不能在派生类中访问。
不管继承方式如何,基类中的private成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)
由于private和protected继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以,在实际开发中,一般使用public。
注意:using只能改变基类中public和protected成员的访问权限,不能改变private成员的访问权限,因为基类中的private成员在派生类中是不可见的,根本不能使用。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class A { // 基类
public:int m_a=10;
protected:int m_b=20;
private:int m_c = 30;
};class B :public A // 派生类
{
public:using A::m_b; // 把m_b的权限修改为公有的。
private:using A::m_a; // 把m_a的权限修改为私有的。
};int main()
{B b; // b.m_a = 11;b.m_b = 21;//b.m_c = 21;
}
父成子,子成父
class Base
{
public:Base(){cout<<"父类构造"<cout<<"父类析够"<
public:Other(){cout<<"Other构造"<cout<<"Other析够"<
public:Other ob;
public:
public:Son(){cout<<"Son构造"<cout<<"Son析够"<Son ob;return 0;
}
注意:派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class A { // 基类
public:int m_a;
private:int m_b;
public:A() : m_a(0) , m_b(0) // 基类的默认构造函数。{ cout << "调用了基类的默认构造函数A()。\n"; }A(int a,int b) : m_a(a) , m_b(b) // 基类有两个参数的构造函数。{ cout << "调用了基类的构造函数A(int a,int b)。\n"; }A(const A &a) : m_a(a.m_a+1) , m_b(a.m_b+1) // 基类的拷贝构造函数。{cout << "调用了基类的拷贝构造函数A(const A &a)。\n";}// 显示基类A全部的成员。void showA() { cout << "m_a=" << m_a << ",m_b=" << m_b << endl; }
};class B :public A // 派生类
{
public:int m_c;B() : m_c(0) , A() // 派生类的默认构造函数,指明用基类的默认构造函数(不指明也无所谓)。{cout << "调用了派生类的默认构造函数B()。\n";}B(int a, int b, int c) : A(a, b), m_c(c) // 指明用基类的有两个参数的构造函数。{cout << "调用了派生类的构造函数B(int a,int b,int c)。\n";}B(const A& a, int c) :A(a), m_c(c) // 指明用基类的拷贝构造函数。{cout << "调用了派生类的构造函数B(const A &a,int c) 。\n";}// 显示派生类B全部的成员。void showB() { cout << "m_c=" << m_c << endl << endl; }
}; int main()
{B b1; // 将调用基类默认的构造函数。b1.showA(); b1.showB();B b2(1, 2, 3); // 将调用基类有两个参数的构造函数。b2.showA(); b2.showB();A a(10, 20); // 创建基类对象。B b3(a, 30); // 将调用基类的拷贝造函数。b3.showA(); b3.showB();
}
子类实例化对象时 会自动调用 成员对象、父类的默认构造
。
子类实例对象时 必须使用初始化列表 调用成员对象、父类的有参构造。
初始化列表时:父类写类名称 成员对象用对象名
#include
using namespace std;
class Base
{
public:int a;
public:Base(){cout<<"父类默认构造"<this->a = a;cout<<"父类有参构造"<cout<<"父类析够"<
public:int b;
public:Other(){cout<<"Other默认构造"<this->b = b;cout<<"Other有参构造"<~Other()cout<<"Other析够"<
public:Other ob;int c;
public:Son(){cout<<"Son构造"<this->c = c;cout<<"Son有参构造"<cout<<"Son析够"<Son ob(10,20,30);return 0;
}
同名成员 最简单 最安全的处理方式:加作用域
类是一种作用域,每个类都有它自己的作用域,在这个作用域之内定义成员。
子类默认优先访问 子类的同名成员
必须加父类作用域 访问父类的同名成员
class Base
{
public:int a;
public:Base(int a){this->a = a;}
};
class Son:public Base
{
public:int a;Son(int x, int y):Base(x){a = y;}
};
int main()
{Son ob(10,20);//子类默认优先访问 子类的同名成员cout<
class Base
{
public:void fun01(){cout<<"Base 无参的fun01"<
public:void fun01(){cout<<"Son 无参的fun01"<Son ob;//子类默认优先访问 子类的同名成员ob.fun01();//必须加父类作用域 访问父类的同名成员ob.Base::fun01();return 0;
}
重载:无继承,同一作用域,参数的个数不同、顺序不同、类型不同 都可重载
重定义:有继承, 子类 重定义 父类的同名函数(参数可以不同)(非虚函数)子类一旦 重定义了父类 的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。
class Base
{
public:void fun01(){cout<<"Base 无参的fun01"<cout<<"Base 的fun01 int"<cout<<"Base 的fun01 int int"<
public:void fun01(string a){cout<<"Son 的fun01 char"<Son ob;ob.fun01("hello");ob.fun01();ob.fun01(10);ob.fun01(10,20);return 0;
}
需要加父类的作用域 才能识别 屏蔽的函数
不是所有的函数都能自动从基类继承到派生类中。
构造函数和析构函数
用来处理对象的创建和析构操
作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继
承,必须为每一个特定的派生类分别创建。
另外operator=
也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。 在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。
不提倡使用多继承,只有在比较简单和不出现二义性的情况时才使用多继承,能用单一继承解决的问题就不要使用多继承。
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争
议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
class 父类1{};
class 父类2{};
class 子类:继承方式1 父类1, 继承方式2 父类2
{
//新增子类数据
};
子类就拥有了父类1,父类2的大部分数据.
class Base1
{
public:int a;
};
class Base2
{
public:int b;
};
class Son:public Base1, public Base2
{
public:};
int main()
{Son ob;//局部对象没有初始化,参数是随机的cout<
如果多继承中 遇到同名成员 需要加父类作用域解决。
class Base1
{
public:int a;Base1(int a):a(a){}
};
class Base2
{
public:int a;Base2(int a):a(a){}
};
class Son:public Base1, public Base2
{
public:int a;Son(int a, int b,int c):Base1(a),Base2(b),a(c){}
};
int main()
{Son ob(10,20,30);cout<
有了多继承,就存在菱形继承
菱形继承:有公共祖先的继承 叫菱形继承。
最底层的子类 数据 会包含多份(公共祖先的数据)
class Animal
{
public:int data;
};
class Sheep :public Animal{};
class Tuo :public Animal {};
class SheepTuo:public Sheep,public Tuo{};
int main()
{SheepTuo ob;memset(&ob, 0, sizeof(SheepTuo));//cout << ob.data << endl;//二义性cout << ob.Sheep::data << endl;cout << ob.Tuo::data << endl;
}
怎么才能只要公共祖先的一份数据呢?
虚继承 解决 菱形继承中 多份公共祖先数据的问题。
虚继承可以解决菱形继承的二义性和数据冗余的问题。
在继承方式 前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。
#include
#includeusing namespace std;
class Animal
{
public:int data;
};
class Sheep :virtual public Animal{
};
class Tuo :virtual public Animal {
};
class SheepTuo:public Sheep,public Tuo{};
int main()
{SheepTuo ob;cout << ob.data << endl;return 0;
}
1、打开命令行开发模式
2、找到类所在的源文件路径
3、在命令行中 导出类的布局
cl /d1 reportSingleClassLayoutAnimal main.cpp
Animal布局:
Sheep布局:
Tuo布局:
SheepTuo布局:
虚继承 会在子类中产生 虚基类指针(vbptr) 指向 虚基类表(vbtable), 虚基类表纪录的是通过该指针访
问公共祖先的数据的偏移量。
注意:
派生类和基类之间有一些特殊关系。
2)可以把派生类对象赋值给基类对象(包括私有成员),但是,会舍弃非基类的成员。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class A { // 基类
public:int m_a=0;
private:int m_b=0;
public:// 显示基类A全部的成员。void show() { cout << "A::show() m_a=" << m_a << ",m_b=" << m_b << endl; }// 设置成员m_b的值。void setb(int b) { m_b = b; }
};class B :public A // 派生类
{
public:int m_c=0;// 显示派生类B全部的成员。void show() { cout << "B::show() m_a=" << m_a << "m_c=" << m_c << endl; }
};int main()
{B b;A a;b.m_a = 10; b.setb(20); // 设置成员m_b的值。b.m_c = 30;a->show(); // 调用的是A类的show()函数。a = b; //派生类对象赋值给基类对象a->show(); // 调用的是A类的show()函数。//结果:m_a = 0,m_b = 0//m_a = 10,m_b = 20
}
3)基类指针可以在不进行显式转换的情况下指向派生类对象。
4)基类引用可以在不进行显式转换的情况下引用派生类对象。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class A { // 基类
public:int m_a=0;
private:int m_b=0;
public:// 显示基类A全部的成员。void show() { cout << "A::show() m_a=" << m_a << ",m_b=" << m_b << endl; }// 设置成员m_b的值。void setb(int b) { m_b = b; }
};class B :public A // 派生类
{
public:int m_c=0;// 显示派生类B全部的成员。void show() { cout << "B::show() m_a=" << m_a << "m_c=" << m_c << endl; }
};int main()
{B b;A* a = &b;//基类指针指向派生类对象b.m_a = 10; b.setb(20); // 设置成员m_b的值。b.m_c = 30;b.show(); // 调用的是B类的show()函数。a->m_a = 11;a->setb(22); // 设置成员m_b的值。基类指针或引用只能调用基类的方法,不能调用派生类的方法。// a->m_c = 30;a->show(); // 调用的是A类的show()函数。
}
注意:
1)基类指针或引用只能调用基类的方法,不能调用派生类的方法。
2)可以用派生类构造基类。
3)如果函数的形参是基类,实参可以用派生类。
4)C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。但是,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针(没有价值,没有讨论的必要)。
多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。 多态性(polymorphism)提供
接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组
织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新
的功能时也能扩展。
静态多态(编译时多态,早绑定):函数重载、运算符重载、重定义
动态多态(运行时多态,晚绑定):虚函数
基类指针只能调用基类的成员函数,不能调用派生类的成员函数。如果在基类的成员函数前加virtual 关键字,把它声明为虚函数,基类指针就可以调用派生类中同名的成员函数,通过派生类中同名的成员函数,就可以访问派生对象的成员变量。
有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,基类指针表现出了多种形式,这种现象称为多态。
基类引用也可以使用多态。
注意:
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。class CAllComers { // 报名者类
public:int m_bh = 0; // 编号。virtual void show() { cout << "CAllComers::show():我是" << m_bh << "号。 " << endl; }virtual void show(int a) { cout << "CAllComers::show(int a):我是" << m_bh << "号。 " << endl; }
};class CGirl :public CAllComers { // 超女类
public:int m_age = 0; // 年龄。void show() { cout << "CGirl::show():我是" << m_bh << "号, " << m_age << "岁。" << endl; }void show(int a) { cout << "CGirl::show(int a):我是" << m_bh << "号, " << m_age << "岁。" << endl; }//函数重载
};int main()
{ CAllComers a; a.m_bh = 3; // 创建基类对象并对成员赋值。CGirl g; g.m_bh = 8; g.m_age = 23; // 创建派生类对象并对成员赋值。CAllComers* p; // 声明基类指针。//p = &a; p->show(); // 让基类指针指向基类对象,并调用虚函数。p = &g; p->show(); // 让基类指针指向派生类对象,并调用虚函数。p->show(5); p->CAllComers::show(5);CAllComers &ra = a;ra.show(); // 声明基类引用。
CAllComers &rg = g;rg.show(); // 声明基类引用。}
父类指针(引用)保存 子类空间地址的目的 就是让算法通用。
class Animal
{
public:void speak(void){cout<<"动物在说话"<
public:
//函数功能重写void speak(void){cout<<"狗在汪汪"<Animal *p = new Dog;p->speak();//动物在说话
}
其实用户的需求:p->speak 希望等到的是“狗在汪汪” 而不是“动物在说话”。原因在此:
指针变量能操作的空间大小由指针变量指向的类型决定,所以p只能操作父类的这一部分数据
成员函数前加virtual修饰
class Animal
{
public://虚函数virtual void speak(void){cout<<"动物在说话"<
public://子类重写 父类的虚函数:函数名、返回值类型、参数类型个数顺序 必须完全一致//虚函数可以写virtual也可以不写virtual void speak(void){cout<<"狗在汪汪"<
public://子类重写 父类的虚函数void speak(void){cout<<"猫在喵喵"<Animal *p1 = new Dog;p1->speak();//"狗在汪汪"Animal *p2 = new Cat;p2->speak();//"猫在喵喵"
}
多态条件:有继承、子类重写父类的虚函数,父类指针 指向子类空间。
Animal的类的结构:
如果一个类中的成员函数 被virtual修饰,那么这个函数就是虚函数。类就会产生一个虚函数指针(vfptr)指向了一张虚函数表(vftable)。如果这个(父)类 没有涉及到继承, 这时虚函数表中 纪录就是当前虚函数入口地址。
Dog的类存结构:
涉及到继承时,子类会把父类中的虚函数指针和虚函数表继承,并修改虚函数表中重写的虚函数入口地址
本质上还是调用父类的虚函数,只是该虚函数指针指向的地址修改了
#include
using namespace std;
class Animal
{
public://虚函数virtual void speak(void){cout<<"动物在说话"<
public:
#if 1//子类重写 父类的虚函数void speak(void){cout<<"狗在汪汪"<
public://子类重写 父类的虚函数void speak(void){cout<<"猫在喵喵"<p->speak();return;
}
int main(int argc, char *argv[])
{AnimalSpeak(new Dog);//狗在汪汪AnimalSpeak(new Cat);//猫在喵喵return 0;
}
重载:同一作用域,同名函数,参数的顺序、个数、类型不同 都可以重载。函数的返回值类型不能作为重载条件(函数重载、运算符重载)
重定义:有继承,子类 重定义 父类的同名函数(非虚函数)
, 参数顺序、个数、类型可以不同
。子类的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)
重写(覆盖):有继承,子类 重写 父类的虚函数
。返回值类型、函数名、参数顺序、个数、类型都必须一致
。
通过上面的讲解发现,如果基类一定派生出子类,而子类一定会重写父类的虚函数,那么父类的虚函数
中的函数体感觉是无意义,可不可以不写父类虚函数的函数体呢?可以的,那就必须了解纯虚函数。
目的:提供接口
class Animal
{
public://纯虚函数,不写函数体但又要和函数声明区别开来virtual void speak(void)=0;
};
一旦类中有纯虚函数,那么这个类 就是抽象类。
抽象类 不能实例化 对象。(Animal ob;错误)
抽象类 必须被继承 同时 子类 必须重写 父类的所有纯虚函数,否则 子类也是抽象类。
#include
using namespace std;
//有 纯虚函数的类 为抽象类class Animal
{
public://纯虚函数virtual void speak(void)=0;
};
class Dog:public Animal
{
public://子类一定要重写 父类的所有纯虚函数void speak(void){cout<<"狗在汪汪"<//Animal ob;//err 抽象类不能实例化对象Animal *p = new Dog;p->speak();//"狗在汪汪"return 0;
}
抽象类主要的目的 是设计 类的接口:
#include
using namespace std;
//抽象制作饮品class AbstractDrinking{
public://烧水virtual void Boil() = 0;//冲泡virtual void Brew() = 0;//倒入杯中virtual void PourInCup() = 0;//加入辅料virtual void PutSomething() = 0;//规定流程void MakeDrink(){this->Boil();Brew();PourInCup();PutSomething();}
};
//制作咖啡
class Coffee : public AbstractDrinking{
public://烧水virtual void Boil(){cout << "煮农夫山泉!" << endl;}//冲泡virtual void Brew(){cout << "冲泡咖啡!" << endl;}//倒入杯中virtual void PourInCup(){cout << "将咖啡倒入杯中!" << endl;}//加入辅料virtual void PutSomething(){cout << "加入牛奶!" << endl;}
};
//制作茶水
class Tea : public AbstractDrinking{
public://烧水virtual void Boil(){cout << "煮自来水!" << endl;}//冲泡virtual void Brew(){cout << "冲泡茶叶!" << endl;}//倒入杯中virtual void PourInCup(){cout << "将茶水倒入杯中!" << endl;}//加入辅料virtual void PutSomething(){cout << "加入食盐!" << endl;}
};
//业务函数
void DoBussiness(AbstractDrinking* drink){drink->MakeDrink();delete drink;//释放空间
}
int main(int argc, char *argv[])
{DoBussiness(new Coffee);cout << "--------------" << endl;DoBussiness(new Tea);return 0;
}
虚函数:virtual修饰 有函数体 不会导致父类为抽象类。
纯虚函数:virtual修饰,=0,没有函数体 导致父类为抽象类。子类必须重写父类的所有纯虚函数。
virtual修饰析构函数
虚析构:通过父类指针 释放整个子类空间。
class Animal
{
public://虚函数virtual void speak(void){cout << "动物在说话" << endl;}//虚析构virtual ~Animal(){cout<<"Animal的析构函数"<
public://子类重写 父类的虚函数void speak(void){ cout << "狗在汪汪" << endl;}{~Dog()cout<<"Dog的析构函数"<Animal* p1 = new Dog;p1->speak();//p指向的空间是父类,没有虚析构只能释放父类的空间//虚析构,通过父类指针释放子类所有空间delete p1;
}
构造的顺序:父类—>成员---->子类
析构的顺序:子类—>成员---->父类
不需要重写,虽然析构函数也属于成员函数
纯虚析构的本质:是析构函数,各个类的回收工作。而且析构函数不能被继承。
必须为纯虚析构函数提供一个函数体(因为虚构函数的存在意义是为了完成清理类的工作)。
纯虚析构函数 必须在类外实现.
#include
using namespace st ;
class Animal
{
public://纯虚函数virtual void speak(void)=0;//纯虚析构函数 必须在类外实现virtual ~Animal()=0;
};
class Dog :public Animal
{
public://子类重写 父类的虚函数void speak(void){cout << "狗在汪汪" << endl;}~Dog(){cout<<"Dog的析构函数"<cout<<"Animal的析构函数"<Animal* p1 = new Dog;p1->speak();delete p1;return 0;
}
虚析构:virtual修饰,有函数体,不会导致父类为抽象类。
纯虚析构:virtual修饰,=0,函数体必须类外实现,导致父类为抽象类。
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形 参类型不具体制定,用一个虚拟的类型来代表
。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。 c++提供两种模板机制:函数模板和类模板 类属 - 类型参数化,又称参数模板
总结:
c++面向对象编程思想:封装、继承、多态
c++泛型编程思想:模板
其实就是一个接口可以操作多种数据类型【即不关心数据类型】
模板关键字template
//T只能对当前函数有效,可以分行也可以不分行
template
void swapAll(T &a, T &b)
{T tmp = a;a = b;b=tmp;return;
}
int main()
{int a = 10, b = 20;//函数调用时 根据实参的类型 会自动推导T的类型swapAll(a, b);cout<
函数模板 会编译两次:
函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
1、函数模板 和 普通函数 都识别。(优先选择 普通函数)
//T只能对当前函数有效 typename可以换成class
template
void swapAll(T &a, T &b)
{T tmp = a;a = b;b=tmp;cout<<"函数模板"<int tmp = a;a = b;b=tmp;cout<<"普通函数"<int a = 10, b = 20;swapAll(a, b);//调用普通函数cout<
2、函数模板 和 普通函数 都识别。强制使用函数模板
int main()
{int a = 10, b = 20;//强制使用函数模板swapAll<>(a, b);//调用函数模板,系统推倒swapAll(a, b);//人为推导cout<
3 、函数模板 自动类型推导时 不能对函数的参数 进行 自动类型转换。
template
void myPrintAll(T a, T b)
{cout<cout<myPrintAll(10, 20);//普通函数myPrintAll('a','b');//函数模板myPrintAll(10,'b');//普通函数,调用函数模板会失败因为推导失败//强制说明T为int类型 就支持自动类型转换myPrintAll(10,'b');//函数模板
}
template
void myPrintAll(T a)
{cout<
void myPrintAll(T a, T b)
{cout<
当函数模板 推导出 T为数组或其他自定义类型数据 可能导致运算符 不识别。
解决办法一:运算符重载(推荐)
#include
using namespace std;
class Data
{friend ostream& operator<<(ostream& out, Data ob);
private:int data;
public:Data(){}Data(int data){this->data = data;}
};
ostream& operator<<(ostream& out, Data ob)
{out<
void myPrintAll(T a)
{cout<myPrintAll(10);//10myPrintAll('a');//'a'Data ob1(100);myPrintAll(ob1);//100return 0;
}
解决办法二:具体化函数模板
template
void myPrintAll(T a)
{cout<friend void myPrintAll(Data a);
private:int data;
public:Data(){}Data(int data)
{this->data = data;
}
};
//函数模板具体化,默认先调用上面的函数模板,当识别到数据类型是Data时
template<> void myPrintAll(Data a)
{cout<myPrintAll(10);myPrintAll('a');Data ob1(100);myPrintAll(ob1);return 0;
}
类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同
的,仅仅是数据类型不同。 类模板用于实现类所需数据的类型参数化。
//类模板
template
class Data
{
private:T1 a;T2 b;
public:Data(){}Data(T1 a, T2 b){this->a = a;this->b = b;}void showData(){cout<
类模板 实例化对象 不能自动类型推导(重要),要指定模板的数据类型。
类模板的成员函数在类外实现时,不管成员函数是有参还是无参,都需要加类的作用域修饰,有固定格式。需要重新写上template
//类模板
template
class Data
{
private:T1 a;T2 b;
public:Data(){}Data(T1 a, T2 b);void showData();
};
template
Data::Data(T1 a, T2 b)
{this->a = a;this->b = b;
}
template
void Data::showData()
{cout<
//类模板
template
class Data
{templatefriend void myPrintData(Data &ob);
private:T1 a;T2 b;
public:Data(){}Data(T1 a, T2 b);void showData();
};
//函数模板
template void myPrintData(Data &ob)
{cout<//类模板实例化对象调用有参构造Data ob1(100, 'A');myPrintData(ob1);
}
//类模板
template
class Data
{friend void myPrintData(Data &ob);
private:T1 a;T2 b;
publicData(){}Data(T1 a, T2 b);void showData();
};
//普通函数
void myPrintData(Data &ob)
{cout<Data ob1(100, 'A');myPrintData(ob1);
}
原因是经过2次编译,所以最好不要将模板头文件与源文件分离
类模板的头文件一般是类名称和函数的结合体,所以文件命名为.hpp
data.hpp
#ifndef DATA_H
#define DATA_H
#include
using namespace std;
template
class Data
{
private:T1 a;T2 b;
public:Data();//在data.cpp文件中生成时要加上类的参数类型,例如DataData(T1 a, T2 b);void showData(void);
};
template
Data::Data()
{cout<<"无参构造"<
Data::Data(T1 a, T2 b)
{this->a = a;this->b = b;
}
template
void Data::showData(void)
{cout<
main.cpp
#include
#include"data.hpp"using namespace std;
int main(int argc, char *argv[])
{Data ob1(100,'A');ob1.showData();return 0;
}
数组类模板:可以存放任意数据类型
myarray.hpp
#ifndef MYARRAY_HPP
#define MYARRAY_HPP
#include
#include
#include
using namespace std;
template
class MyArray
{templatefriend ostream& operator<<(ostream& out, MyArray ob);
private:T *arr;//保存数组首元素地址int size;//大小int capacity;//容量
public:MyArray();MyArray(int capacity);MyArray(const MyArray &ob);~MyArray();MyArray& operator=(MyArray &ob);void pushBack(T elem);void sortArray();
};
#endif // MYARRAY_HPP
template
MyArray::MyArray()
{capacity = 0;size = 0;arr = NULL;
}
template
MyArray::MyArray(int capacity)
{this->capacity = capacity;size = 0;arr = new T[this->capacity];memset(arr, 0,sizeof(T)*capacity);
}
template
MyArray::MyArray(const MyArray &ob)
{if(ob.arr == NULL){arr = NULL;size = 0;capacity=0;}else{capacity = ob.capacity;size = ob.size;arr = new T[capacity];memcpy(arr, ob.arr, sizeof(T)*capacity);}
}
template
MyArray::~MyArray()
{if(arr != NULL){delete [] arr;}
}
template
MyArray &MyArray::operator=(MyArray &ob)
{//判断this->arr是否存在空间if(arr != NULL){delete [] arr;arr=NULL;}size = ob.size;capacity = ob.capacity;arr = new T[capacity];memset(arr,0,sizeof(T)*capacity);memcpy(arr, ob.arr, sizeof(T)*capacity);return *this;
}
template
void MyArray::pushBack(T elem)
{if(size==capacity)//满{//扩展容量capacity = (capacity==0?1:2*capacity) ;//申请空间T *tmp = new T[capacity];if(arr != NULL){//将旧空间的内容拷贝到新空间memcpy(tmp,arr,sizeof(T)*size);//释放旧空间delete [] arr;}arr = tmp;}arr[size] = elem;size++;return;
}
template
void MyArray::sortArray()
{if(arr == NULL){cout<<"容器为空间"<int i=0,j=0;for(i=0;ifor(j=0;jif(arr[j] > arr[j+1]){T tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}}}return;
}
template
ostream& operator<<(ostream& out, MyArray ob)
{int i=0;for(i=0;iout<
main.cpp
#include
#include "myarray.hpp"using namespace std;
class Person
{friend ostream& operator<<(ostream& out, Person ob);
private:int num;string name;float score;
public:Person(){}Person(int num,string name, float score){this->num = num;this->name = name;this->score = score;}bool operator>(const Person &ob){return num>ob.num;}
};
ostream& operator<<(ostream& out, Person ob)
{int i=0;out<MyArray ob1(5);ob1.pushBack(10);ob1.pushBack(30);ob1.pushBack(20);ob1.pushBack(50);ob1.pushBack(40);cout< ob2(5);ob2.pushBack('A');ob2.pushBack('C');ob2.pushBack('D');ob2.pushBack('B');ob2.pushBack('F');cout< ob3;ob3.pushBack(Person(100,"lucy", 88.8f));ob3.pushBack(Person(103,"bob", 89.9f));ob3.pushBack(Person(105,"tom", 98.8f));ob3.pushBack(Person(102,"德玛", 99.8f));cout<
#include
using namespace std;
//类模板template
class Base
{
private:T1 a;T2 b;
public:Base(){}Base(T1 a, T2 b);void showData();
};
template
Base::Base(T1 a, T2 b)
{this->a = a;this->b = b;
}
template
void Base::showData()
{cout<
{
public:int c;
public:
//初始化列表Son1(int a, char b, int c):Base(a, b){this->c = c;}
};
int main(int argc, char *argv[])
{Son1 ob1(100, 'A', 200);ob1.showData();cout<
#include
using namespace std;
//类模板template
class Base
{
private:T1 a;T2 b;
public:Base(){}Base(T1 a, T2 b);void showData();
};
template
Base::Base(T1 a, T2 b)
{this->a = a;this->b = b;
}
template
void Base::showData()
{cout<
class Son1:public Base
{
public:T3 c;
public:Son1(T1 a, T2 b, T3 c):Base(a, b){this->c = c;}
};
int main(int argc, char *argv[])
{Son1 ob1(100, 'A', 200);ob1.showData();cout<
整型从低到高:
har -> short -> int -> long -> long long
浮点型从低到高:
loat -> double -> long double
自动类型转换的规则如下:
当表达式中含有浮点型操作数时,所有操作数都将转换为浮点型。
赋值运算的右值类型与左值类型不一致时,将右值类型提升/降低为左值类型。
强制类型转换的语法:(目标类型)表达式或目标类型(表达式)
标准c++提供了一个显示的转换的语法,来替代旧的C风格的类型转换。 使用C风格的强制转换可以把想
要的任何东西转换成我们需要的类型。那为什么还需要一个新的C++类型的强制转换呢? 新类型的强制 转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。C++风格的强制转换其他 的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码,就能立即知道一个强制 转换的目的
class Base{};
class Son:public Base{};
class Other{};
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
基本类型:支持
int num = static_cast(3.14);//ok
上行转换:支持 安全
Base *p = static_cast (new Son);
下行转换:支持 (不安全)
Son *p2 = static_cast(new Base);
不相关类型转换:不支持
Base *p3 = static_cast (new Other);//err
dynamiccast主要用于类层次间的上行转换和下行转换
基本类型:不支持
int num = dynamic_cast(3.14);//err
上行转换:支持
Base *p1 = dynamic_cast (new Son);//ok
下行转换:不支持(不安全)
Son *p2 = dynamic_cast(new Base);//err
不相关类型转换:不支持
Base *p3 = dynamic_cast (new Other);//err
1、将const修饰的指针或引用 转换成 非const (支持)
const int *p1;
int *p2 = const_cast(p1);
const int &ob = 10;
int &ob1 = const_cast(ob);
2、将非const修饰的指针或引用 转换成 const (支持)
int *p3;
const int *p4 = const_cast(p3);
int data = 10;
const int &ob2 = const_cast(data);
遇到错误 抛出异常 捕获异常
异常:是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不
存在,空指针,内存不足,访问非法内存等等)。(异常是一个类)
c++异常机制相比C语言异常处理的优势?
try
{throw 异常值;
}
catch(异常类型1 异常值1)
{处理异常的代码1;
}
catch(异常类型2 异常值2)
{处理异常的代码2;
}
catch(...)//任何异常都捕获
{处理异常的代码3;
}
int ret = 0;
try
{//throw 1;//throw 'A';throw 2.14f;
}
catch(int e)//捕获
{cout<<"int异常值为:"<cout<<"char异常值为:"<cout<<"其他异常值为:"<
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构【局部对象会析构】。析构的顺序与构造的顺序相反,这一过程称为栈的解旋.
try
{Data ob1;Data ob2;Data ob3;throw 1;//抛出异常后 ob3 ob2 ob1依次自动释放(栈解旋)
}
#include
using namespace std;
class Data
{
public:int a;
public:Data(){}Data(int a){this->a = a;cout<<"构造函数"<cout<<"析构函数"<int ret = 0;try{Data ob1(10);Data ob2(20);Data ob3(30);throw 1;}catch(int)//捕获{cout<<"int异常值为:"<cout<<"char异常值为:"<cout<<"其他异常值为:"<
异常的接口声明:描述的是 可以抛出哪些类型的异常
void fun01()
{//throw 1;//throw '1';throw "hello";
}
void fun02() throw(int,char)
{//throw 1;//throw '1';throw "hello";//抛出 不能捕获
}
void fun03() throw()
{throw 1;//throw '1';//throw "hello";//抛出 不能捕获
}
class MyException
{
public:MyException(){cout << "异常变量构造" << endl;};MyException(const MyException & e){cout << "拷贝构造" << endl;}~MyException(){cout << "异常变量析构" << endl;}
};
抛出匿名对象,用引用去接。1、引用不占用内存空间,2、不会发生拷贝构造,3、栈空间会自动释放
//异常基类
class BaseException{
public:virtual void printError(){};
};
//空指针异常
class NullPointerException : public BaseException{
public:virtual void printError(){cout << "空指针异常!" << endl;}
};
//越界异常
class OutOfRangeException : public BaseException{
public:virtual void printError(){cout << "越界异常!" << endl;}
};
void doWork(){//throw NullPointerException();throw OutOfRangeException();
}
int main()
{try{doWork();}catch (BaseException& ex)//父类引用去接 可以捕获搭配该父类派生出的所有子类的子类{ex.printError();}
}
异常名称 | 描述 |
---|---|
exception | 所有标准异常类的父类 |
bad_alloc | 当operator new and operator new[],请求分配内存失败时 |
bad_exception | 这是个特殊的异常,如果函数的异常抛出列表里声明了badexception异常,当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数 中若抛出异常,不论什么类型,都会被替换为badexception类型 |
bad_typeid | 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常 |
bad_cast | 使用dynamic_cast转换引用失败的时候 |
ios_base::failure | io操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
logic_error的子类
异常名称 | 描述 |
---|---|
length_error | 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作 |
domain_error | 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数 |
outofrange | 超出有效范围 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
runtime_error的子类
异常名称 | 描述 |
---|---|
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
基于标准异常基类 编写自己的异常类。
重写父类的what要加const throw否则会抛出系统的标准异常
class NewException:public exception
{
private:string msg;
public:NewException(){}NewException(string msg){this->msg = msg;}//重写父类的whatvirtual const char* what()const throw()//防止父类在子类前抛出标准异常{//将string类转换成char *//c_str()是string类自带的成员函数return this->msg.c_str();}~NewException(){}
};
int main()
{try{throw NewException("哈哈,自己的异常");}catch(exception &e){cout<
长久以来,软件界一直希望建立一种可重复利用的东西,以及一种得以制造出”可重复运用的东西”的方
法,让程序员的心血不止于随时间的迁移,人事异动而烟消云散,从函数(functions),类别(classes),函数
库(function libraries),类别库(classlibraries)、各种组件,从模块化设计,到面向对象(object oriented),为的就是复用性的提升。
复用性必须建立在某种标准之上。但是在许多环境下,就连软件开发最基本的数据结构(datastructures) 和算法(algorithm)都未能有一套标准。大量程序员被迫从事大量重复的工作,竟然是为了完成前人已经完成而自己手上并未拥有的程序代码,这不仅是人力资源的浪费,也是挫折与痛苦的来源。
为了建立数据结构【即容器,对数据的存取】和算法【对数据的操作】的一套标准,并且降低他们之间的耦合关系,以提升各自的独立性、弹性、交互操作性(相互合作性,interoperability),诞生了 STL
STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统 称。现在主要出现
在 c++中,但是在引入 c++之前该技术已经存在很长时间了。 STL 从广义上分为: 容器(container) 算法 (algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接
。STL 几乎所有的代码都采用了模 板类或者模板函数
,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。STL(Standard
Template Library)标准模板库,在我们 c++标准程序库中隶属于 STL的占到了 80%以上。
这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
1、容器:存放数据
2、算法:操作数据
3、迭代器:算法 通过迭代器 操作容器数据
4、仿函数:为算法提供更多的策略
5、适配器:为算法提供更多参数的接口
6、空间配置器:为算法和容器 动态分配、管理空间
STL 的一个重要特性是将数据和操作分离。数据由容器类别加以管理,操作则由 特定的算法完成。
容器与迭代器一一对应
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等 等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍 历、寻找极值等
C 风格字符串(以空字符结尾的字符数组)太过复杂难于掌握,不适合大程序的开发,所以 C++标准库定义 了一种 string 类,定义在头件。
String 和 c 风格字符串对比:
内部已经重写了cout<<对象
1、string 构造函数
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个 string 对象初始化另一个 string 对象
string(const char* s);//使用字符串 s 初始化
string(int n, char c);//使用 n 个字符 c 初始化 v
2、string 基本赋值操作
//重写赋值操作符
string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串 s 赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
//成员函数赋值
string& assign(const char *s);//把字符串 s 赋给当前的字符串
string& assign(const char *s, int n);//把字符串 s 的前 n 个字符赋给当前的字符串
string& assign(const string &s);//把字符串 s 赋给当前字符串
string& assign(int n, char c);//用 n 个字符 c 赋给当前字符串
string& assign(const string &s, int start, int n);//将 s 从 start 开始 n 个字符赋值给字符串
返回值是引用可以完成链式操作
举例:
3、string 存取
字符操作
char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过 at 方法获取字符
返回值是引用(对象的别名)
[] 越界不会抛出异常 ,at方法 越界会抛出异常
4、string 拼接操作
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串 s 连接到当前字符串结尾
string& append(const char *s, int n);//把字符串 s 的前 n 个字符连接到当前字符串结尾
string& append(const string &s);//同 operator+=()
string& append(const string &s, int pos, int n);//把字符串 s 中从 pos 开始的 n 个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加 n 个字符 c
5、string 查找和替换
int find(const string& str, int pos = 0) const; //查找 str 第一次出现位置, 从 pos 开始查找
int find(const char* s, int pos = 0) const; //查找 s 第一次出现位置,从 pos开始查找
int find(const char* s, int pos, int n) const; //从 pos 位置查找 s 的前 n 个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符 c 第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找 str 最后一次位置, 从 pos开始查找
int rfind(const char* s, int pos = npos) const;//查找 s 最后一次出现位置,从pos 开始查找
int rfind(const char* s, int pos, int n) const;//从 pos 查找 s 的前 n 个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符 c 最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从 pos 开始 n 个字符为字符串 str
string& replace(int pos, int n, const char* s); //替换从 pos 开始的 n 个字符为字符串s
失败返回-1
6、string 比较操作
/*
compare 函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的 A 比小写的 a 小。
*/
int compare(const string &s) const;//与字符串 s 比较
int compare(const char *s) const;//与字符串 s 比较
重载了> < ==等关运算符 ,可以直接调用运算符进行比较
7、string 子串
string substr(int pos = 0, int n = npos) const;//返回由 pos 开始的 n 个字符组成的字符串
8、string 插入和删除操作
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入 n 个字符 c
string& erase(int pos, int n = npos);//删除从 Pos 开始的 n 个字符
9、string 和 c-style 字符串转换
//string 转 char*
string str = "itcast";
const char* cstr = str.c_str();//char* 转 string
char* s = "itcast";
string str(s);
在 c++中存在一个从 const char 到 string 的隐式类型转换,却不存在从一个 string对象到 Cstring 的自
动类型转换。对于 string 类型的字符串,可以通过 c_str()函数返回 string 对象对应的 C_string. 通常,程
序员在整个程序中应坚持使用 string 类对象,直到必须将内容转化为 char 时才将其转换为 C_string. 提
示: 为了修改 string 字符串的内容,下标操作符[]和 at 都会返回字符的引用。但当字符串的内存被重新
分配之后,可能发生错误.
string s = "abcdefg";
char& a = s[2];
char& b = s[3];
a = '1';
b = '2';
cout << s << endl;
cout << (int*)s.c_str() << endl;
s = "----------------------------";
//a = '1';
//b = '2';cout << s << endl;
cout << (int*)s.c_str() << endl;
string str = “hehehehe”;
str.size()获取字符串长度
str.capacity()获取容量大小
vector 的数据安排以及操作方式,与 array 非常相似,两者的唯一差别在于空间的运用的灵活性。Array 是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。Vector 是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此 vector 的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的 array了。 Vector 的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦 vector 旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是”配置新空间-数据移动-释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到 vector 的空间配置策略。
v.begin():获取容器的起始迭代器(指向第0个元素)
v.end():获取容器的结束迭代器(指向最后一个元素的下一个位置)
Vector 所采用的数据结构非常简单,线性连续空间,它以两个迭代器 Myfirst 和Mylast 分别指向配置得
来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。 为了降低空
间配置时的速度成本,vector 实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是
容量的概念。换句话说,一个 vector 的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下
次再有新增元素,整个 vector 容器就得另觅居所。
注意: 所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空 间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间
。因此,对 vector 的任何操作,一旦引起空间的重新配置,指向原 vector 的所有迭代器就都失效了。这是程序员容易犯的一个错误,务必小心
1、vector 构造函数
vector v; //采用模板实现类实现,默认构造函数
//容器 的类型需要指定参数类型
vector(v.begin(), v.end());//将 v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将 n 个 elem 拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
定义迭代器
未雨绸缪机制
#include
#include
using namespace std;
int main()
{vector v1;cout<<"容量:"<::iterator it;int i=0;int count =0;for(i=0;i<1000;i++){v1.push_back(i);if(it != v1.begin()){count++;cout<<"第"<
2、vector 常用赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将 vec 与本身的元素互换。
3、vector 大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为 num,若容器变长,则以默认值0填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
4、vector 数据存取操作
at(int idx); //返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[];//返回索引 idx 所指的数据,越界时,运行直接报错front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
5、vector 插入和删除操作
insert(const_iterator pos, int count,ele);//迭代器指向位置 pos 插入 count个元素 ele.
push_back(ele); //尾部插入元素 ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从 start 到 end 之间的元
素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素,容量不会变
6、巧用 swap 收缩内存空间
原理是调用拷贝构造
vector v1;
v1.reserve(1000);
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
cout<<"容量:"<(v1)匿名对象传递参数v1,把一个对象赋值给匿名对象,会发生调用拷贝构造
vector(v1).swap(v1);
cout<<"容量:"<
#include
#include
using namespace std;
int main()
{vector v1(5,10);vector v2(5,100);vector v3(5,1000);//需求:定义一个vector容器存放v1 v2 v3vector< vector > v;v.push_back(v1);v.push_back(v2);v.push_back(v3);//遍历vector< vector >::iterator it=v.begin();for(;it!=v.end();it++){//*it ==vectorvector::iterator mit=(*it).begin();for(;mit!=(*it).end();mit++){cout<<*mit<<" ";}cout<
#include
#include
using namespace std;//使用STL算法对vector容器排序
#include
void printVectorInt(vector &v)
{vector::iterator it=v.begin();for(;it!=v.end();it++){cout<<*it<<" ";}cout<vector v1;v1.push_back(20);v1.push_back(60);v1.push_back(50);v1.push_back(30);v1.push_back(40);v1.push_back(10);printVectorInt(v1);//排序算法sort(v1.begin(), v1.end());printVectorInt(v1);return 0;
}
#include
#include
using namespace std;
#include
class Person
{friend bool comparePerson(Person &ob1, Person &ob2);friend void printVectorPerson(vector &v);
private:int num;string name;float score;
public:Person(){}Person(int num,string name, float score){this->num = num;this->name = name;this->score = score;}
};
void printVectorPerson(vector &v)
{vector::iterator it=v.begin();for(;it!=v.end();it++){//*it==Personcout<<(*it).num<<" "<<(*it).name<<" "<<(*it).score <return ob1.num < ob2.num;
}int main()
{vector v1;v1.push_back(Person(100,"lucy", 77.7f));v1.push_back(Person(103,"bob", 77.7f));v1.push_back(Person(101,"tom", 77.7f));v1.push_back(Person(104,"德玛", 77.7f));v1.push_back(Person(105,"小法", 77.7f));printVectorPerson(v1);//对自定义类型的vector排序 需要指定排序规则sort(v1.begin(), v1.end(), comparePerson);printVectorPerson(v1);return 0;
}
Vector 容器是单向开口的连续内存空间,deque 则是一种双向开口的连续线性空间。所谓的双向开口,
意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector 容器也可以在头尾两端插入元素,
但是在其头部操作效率奇差,无法被接受
Deque 容器和 vector 容器最大的差异,一在于 deque 允许使用常数项时间对头端进行元素的插入和删
除操作。二在于 deque 没有容量的概念
,因为它是动态的以分段连续空间组合而成,随时可以增加一段
新的空间并链接起来,换句话说,像vector 那样,”旧空间不足而重新配置一块更大空间,然后复制元
素,再释放旧空间”这样的事情在 deque 身上是不会发生的。也因此,deque 没有必须要提供所谓的空
间保留(reserve)功能. 虽然 deque 容器也提供了 Random Access Iterator,但是它的迭代器并不是普通
的指针,其复杂度和 vector 不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该
尽可能的使用 vector,而不是deque。对 deque 进行的排序操作,为了最高效率,可将 deque 先完整
的复制到一个 vector 中,对 vector 容器进行排序,再复制回 deque
Deque 容器是连续的空间,至少逻辑上看来如此(物理上不连续,逻辑上连续),连续现行空间总是令我们联想到array 和vector,array 无法成长,vector 虽可成长却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间三步骤,如果不是 vector 每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。 Deque 是由一段一段的定量的连续空间构成
。一旦有必要在deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque 的头端或者尾端。
Deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了
重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。 既然 deque 是分段连续内存空间,
那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。
Deque 代码的实现远比 vector 或 list 都多得多。 Deque 采取一块所谓的map(注意,不是 STL 的 map
容器)作为主控,这里所谓的 map 是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一
个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque 的存储空间的主体。
1、deque 构造函数
deque deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将 n 个 elem 拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
2、deque 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将 deq 与本身的元素互换
3、deque 大小操作
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变
短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置,如
果容器变短,则末尾超出容器长度的元素被删除。
4、deque 双端插入和删除操作
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
5、deque 数据存取
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据
6、deque 插入操作
insert(pos,elem);//在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
7、deque 删除操作
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。
stack 是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示。stack 容器
允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取 stack 的其 他元素
。换言之,stack 不允许有遍历行为
。 有元素入栈的操作称为:push,将元素出 stack 的操作称为pop.
Stack 所有元素的进出都必须符合”先进后出”的条件,只有 stack 顶端的元素,才有机会被外界取用。
Stack 不提供遍历功能,也不提供迭代器。
1、stack 构造函数
stack stkT;//stack 采用模板类实现, stack 对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数
2、stack 赋值操作
stack& operator=(const stack &stk);//重载等号操作符
3、stack 数据存取操作
push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素
4、stack 大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小
Queue 是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新
增元素,从另一端移除元素。
Queue 所有元素的进出都必须符合”先进先出”的条件,只有 queue 的顶端元素,才有机会被外界取用。
Queue 不提供遍历功能,也不提供迭代器。
queue queT;//queue 采用模板类实现,queue 对象的默认构造形式:
queue(const queue &que);//拷贝构造函数
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素
queue& operator=(const queue &que);//重载等号操作符
empty();//判断队列是否为空
size();//返回队列的大小
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接
次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每
个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相较于
vector 的连续线性空间,list 就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者
释放一个元素的空间。因此,list 对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的
元素插入或元素的移除,list 永远是常数时间。 List 和vector 是两个最常被使用的容器。 List 容器是一
个双向链表。
采用动态存储分配,不会造成内存浪费和溢出 链表执行插入和删除操作十分方便,修改指针即可,不需
要移动大量元素 链表灵活,但是空间和时间额外耗费较大。
1、list 构造函数
list lstT;//list 采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将 n 个 elem 拷贝给本身。
list(const list &lst);//拷贝构造函数。
2、 list 数据元素插入和删除操作
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在 pos 位置插 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与 elem 值匹配的元素。
3、list 大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
4、list 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将 lst 与本身的元素互换。
5、list 数据的存取
front();//返回第一个元素。
back();//返回最后一个元素。
6、list 反转排序
reverse();//反转链表,比如 lst 包含 1,3,5 元素,运行此方法后,lst 就包含 5,3,1元素。
sort(); //list 排序
举例
#include
#include
#include
using namespace std;
void printListInt(list &l)
{list::iterator it=l.begin();for(;it!=l.end();it++){cout<<*it<<" ";}cout<list l1;l1.push_back(10);l1.push_back(20);l1.push_back(30);l1.push_front(40);l1.push_front(50);l1.push_front(60);printListInt(l1);//60 50 40 10 20 30//list容器的迭代器是双向迭代器 不支持+2(随机访问迭代器支持) 支持++list::iterator it=l1.begin();//it+2;it++;it++;l1.insert(it, 3, 100);printListInt(l1);//60 50 100 100 100 40 10 20 30//STL提供的算法 只支持随机访问迭代器 而list是双向迭代器 所以sort不支持list//sort(l1.begin(), l1.end());l1.sort();printListInt(l1);//10 20 30 40 50 60 100 100 100l1.reverse();printListInt(l1);
}int main(int argc, char *argv[])
{test01();return 0;
}
set 的特性是。所有元素都会根据元素的键值自动被排序,set 的元素即是键值又是实值。
set 不允许两个元素有相同的键值。
set容器的迭代器是只读迭代器,不允许修改键值,会破坏set的内存布局。
multiset 特性及用法和 set 完全相同,唯一的差别在于它允许键值重复。
1、set 构造函数
set st;//set 默认构造函数:
mulitset mst; //multiset 默认构造函数:
set(const set &st);//拷贝构造函数
2、set 赋值操作
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
3、set 大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空
4、set 插入和删除操作
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为 elem 的元素。
5、set 查找操作
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 set.end();
count(key);//查找键 key 的元素个数
lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器
举例迭代器是只读迭代器,原因是采用平衡二叉树的方法管理数据
#include
#include
#include
using namespace std;
#include
void printSetInt(set &s)
{set::const_iterator it=s.begin();for(;it!=s.end();it++){cout<<*it<<" ";}cout<
public:bool operator()(int v1, int v2){return v1>v2;}
};void printSetInt(set &s)
{//修改排序规则,仿函数既是函数又是类型,MyGreater类会自动生成对象,对象与小括号结合会触发重载函数set::const_iterator it=s.begin();for(;it!=s.end();it++){cout<<*it<<" ";}cout<//set s1;set s1;s1.insert(30);s1.insert(10);s1.insert(20);s1.insert(50);s1.insert(40);printSetInt(s1);}int main(int argc, char *argv[])
{test01();return 0;
}
set存放自定义数据必须修改排序规则
#include
#include
#include
using namespace std;
#include
class MyGreaterPerson;
class Person
{friend class MyGreaterPerson;friend void printSetPerson(set &s);
private:int num;string name;float score;
public:Person(){}Person(int num, string name, float score){this->num = num;this->name = name;this->score = score;}};
class MyGreaterPerson
{
public:bool operator()(Person ob1, Person ob2){return ob1.num < ob2.num;}
};
void printSetPerson(set &s)
{set::const_iterator it=s.begin();for(;it!=s.end();it++){//*it==Personcout<<(*it).num<<" "<<(*it).name<<" "<<(*it).score<//set存放自定义数据必须修改排序set s;s.insert(Person(100,"lucy", 88.8f));s.insert(Person(103,"tom", 88.8f));s.insert(Person(105,"bob", 88.8f));s.insert(Person(104,"德玛", 88.8f));s.insert(Person(102,"小法", 88.8f));printSetPerson(s);}
void test04()
{set s1;s1.insert(10);s1.insert(30);s1.insert(50);s1.insert(70);s1.insert(90);printSetInt(s1);set::const_iterator ret;ret = s1.find(50);if(ret != s1.end()){cout<<"找到的结果为:"<<*ret<test02();return 0;
}
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器
举例
#include
#include
#include
#include
using namespace std;
void test05()
{set s1;s1.insert(10);s1.insert(30);s1.insert(50);s1.insert(70);s1.insert(90);set::const_iterator ret;ret = s1.lower_bound(50);if(ret!=s1.end()){cout<<"下限为:"<<*ret<cout<<"上限为:"<<*ret<::const_iterator , set::const_iterator> pa;pa = s1.equal_range(50);if(pa.first != s1.end()){cout<<"下限为:"<<*(pa.first)<cout<<"上限为:"<<*(pa.second)<test07();return 0;
}
对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用 pair 的两个
公有属性 first 和 second 访问。 类模板:template
//第一种方法创建一个对组
pair pair1(string("name"), 20);
cout << pair1.first << endl; //访问 pair 第一个值
cout << pair1.second << endl;//访问 pair 第二个值
//第二种
pair pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;
Map 的特性是,所有元素都会根据元素的键值自动排序。Map 所有的元素都是pair,同时拥有实值和键
值,pair 的第一元素被视为键值,第二元素被视为实值,map 不允许两个元素有相同的键值。 我们可
以通过 map 的迭代器改变 map 的键值吗?答案是不行,因为 map 的键值关系到 map 元素的排列规
则,任意改变 map键值将会严重破坏 map 组织。如果想要修改元素的实值,那么是可以的。 Map 和
list 拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在
操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。 Multimap 和 map 的操作类
似,唯一区别 multimap 键值可重复。Map 和 multimap 都是以红黑树为底层实现机制。
map容器:每个元素都是 键值-实值 成对存储,自动根据键值排序, 键值不能重复,不能修改。
1、map 构造函数
map mapTT;//map 默认构造函数:
map(const map &mp);//拷贝构造函数
2、map 赋值操作
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
3、 map 大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空
4、 map 插入数据元素操作
map.insert(...); //往容器插入元素,返回 pair
map mapStu;
// 第一种 通过 pair 的方式插入对象
mapStu.insert(pair(3, "小张"));
// 第二种 通过 pair 的方式插入对象
mapStu.inset(make_pair(-1, "校长"));
// 第三种 通过 value_type 的方式插入对象
mapStu.insert(map::value_type(1, "小李"));
// 第四种 通过数组的方式插入值mapStu[3] = "小刘";
mapStu[5] = "小王";
5、 map 删除操作
clear();//删除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中 key 为 keyElem 的对组。
6、map 查找操作
find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回 map.end();
count(keyElem);//返回容器中 key 为 keyElem 的对组个数。对 map 来说,要么是 0,要么是 1。对 multimap 来说,值可能大于 1。
lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器
举例:
#include
#include
vector 的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
deque 的使用场景:比如排队购票系统,对排队者的存储可以采用 deque,支持头端的快速移除,尾端的快速添加。如果采用 vector,则头端移除时,会移动大量的数据,速度慢。 vector 与 deque 的比较: 一:vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置 却是不固定的。 二:如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。
deque 支持头部的快速插入与快速移除,这是 deque 的优点。
list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector 容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对
象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。
注意:
分类:
//函数对象也称为(仿函数)
class Print
{
public:void operator()(char *str){cout<Print ob;ob("hello world");Print()("hello world");//匿名对象
}
总结:
返回值为bool类型的普通函数或仿函数 都叫谓词。
#include
#include
//普通函数
bool greaterThan30(int value)
{return value>30;
}
class GreaterThan30
{
public://仿函数bool operator()(int value){return value>30;}
};
int main()
{vector v1;v1.push_back(10);v1.push_back(30);v1.push_back(50);v1.push_back(70);v1.push_back(90);//find_if条件查找vector::iterator ret;//普通函数提供策略 函数名//ret = find_if(v1.begin(), v1.end(), greaterThan30);//仿函数提供策略 类名称+()ret = find_if(v1.begin(), v1.end(), GreaterThan30());if(ret != v1.end()){cout<<"寻找的结果:"<<*ret<
底层会遍历两个数据
bool myGreaterInt(int v1, int v2)
{return v1>v2;
}
class MyGreaterInt
{
public:bool operator()(int v1, int v2){return v1>v2;}
};
int main()
{vector v1;v1.push_back(10);v1.push_back(50);v1.push_back(30);v1.push_back(90);v1.push_back(70);printVectorAll(v1);//sort(v1.begin(), v1.end(), myGreaterInt);sort(v1.begin(), v1.end(), MyGreaterInt());printVectorAll(v1);
}
STL 内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数
所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。
6个算数类函数对象,除了 negate 是一元运算,其他都是二元运算。
template T plus//加法仿函数
template T minus//减法仿函数
template T multiplies//乘法仿函数
template T divides//除法仿函数
template T modulus//取模仿函数
template T negate//取反仿函数
6个关系运算类函数对象,每一种都是二元运算。
template bool equal_to//等于
template bool not_equal_to//不等于
template bool greater//大于
template bool greater_equal//大于等于
template bool less//小于
template bool less_equal//小于等于
逻辑运算类运算函数,not 为一元运算,其余为二元运算。
template bool logical_and//逻辑与
template bool logical_or//逻辑或
template bool logical_not//逻辑非
举例:
#include
#include
#include //引入
using namespace std;
void printVectorInt(vector &v)
{vector::iterator it=v.begin();for(;it!=v.end();it++){cout<<*it<<" ";}cout<vector v1;v1.push_back(10);v1.push_back(50);v1.push_back(30);v1.push_back(90);v1.push_back(70);printVectorInt(v1);sort(v1.begin(), v1.end(), greater());//类模板要指定参数类型,此时是创建匿名对象printVectorInt(v1);
}
void test03()
{vector v1;v1.push_back(10);v1.push_back(50);v1.push_back(30);v1.push_back(90);v1.push_back(70);vector::iterator ret;//使用适配器绑定参数ret = find_if(v1.begin(), v1.end(), bind2nd(greater(),30));if(ret != v1.end()){cout<<*ret<test02();return 0;
}
适配器 为算法 提供接口。
#include
#include
#include
using namespace std;
//第二步:公共继承binary_function 参数萃取
class printInt:public binary_function
{
public://第三步:整个函数加const修饰void operator()(int value, int tmp) const{cout<<"value="<vector v1;v1.push_back(10);v1.push_back(30);v1.push_back(50);v1.push_back(70);v1.push_back(90);//for_each 提取容器的每个元素//第一步bind2nd 或 bind1st//bind2nd将100绑定到第二个参数tmp行 容器的元素在value上for_each(v1.begin(), v1.end(), bind2nd(printInt(), 100) );cout<
上图是bind2nd,下图是bind1st
普通函数名 作为适配器
函数名在c++中不能代表函数入口地址, 利用函数指针得到函数入口地址
class Data
{
public:int data;
public:Data(){}Data(int d){data = d;}void printInt(int tmp){cout<<"value="<vector v1;v1.push_back(Data(10));v1.push_back(Data(30));v1.push_back(Data(50));v1.push_back(Data(70));v1.push_back(Data(90));//for_each 提取容器的每个元素for_each(v1.begin(), v1.end(), bind2nd(mem_fun_ref(&Data::printInt),100) );cout<
not1 一元取反
not2 二元取反
int main()
{vector v1;v1.push_back(10);v1.push_back(40);v1.push_back(50);v1.push_back(20);v1.push_back(30);//lambda 表达式 c++11才支持//[]里面啥都不写 lambda不能识别 外部数据//[=] lambda能对 外部数据 读操作//[&] lambda能对 外部数据 读写操作for_each(v1.begin(), v1.end(), [&](int val){cout<()));for_each(v1.begin(), v1.end(), [&](int val){cout<
算法的头文件#include 是所有 STL 头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等。
/*
遍历算法 遍历容器元素
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/for_each(iterator beg, iterator end, _callback);
/*
transform 算法 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _cakkback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callback);
回调函数会将形参返回到参数三指定的位置
/*
find 算法 查找元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
*/
find(iterator beg, iterator end, value);
//用迭代器会接返回值
/*
find_if 算法 条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return bool 查找返回 true 否则 false
*/find_if(iterator beg, iterator end, _callback);
/*
adjacent_find 算法 查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
*/
adjacent_find(iterator beg, iterator end, _callback);
//返回值用迭代器去接
/*
binary_search 算法 二分查找法
注意: 在无序序列中不可用
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 查找返回 true 否则 false
*/
bool binary_search(iterator beg, iterator end, value);
//返回值是布尔值
/*
count 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 回调函数或者谓词(返回 bool 类型的函数对象)
@return int 返回元素个数*/
count(iterator beg, iterator end, value);
/*
count_if 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
return int 返回元素个数
*/
count_if(iterator beg, iterator end, _callback);
/*
merge 算法 容器元素合并,并存储到另一容器中,要先开辟第三个容器的空间
注意:两个容器必须是有序的,谁小谁先移动
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
*/
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
/*
sort 算法 容器元素排序
@param beg 容器 1 开始迭代器
@param end 容器 1 结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
*/
sort(iterator beg, iterator end, _callback)
/*
random_shuffle 算法 对指定范围内的元素随机调整次序
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
random_shuffle(iterator beg, iterator end);
可以hi设置随机数种子
/*
reverse 算法 反转指定范围的元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
reverse(iterator beg, iterator end)
/*
copy 算法 将容器内指定范围的元素拷贝到另一容器中
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param dest 目标起始迭代器
*/
copy(iterator beg, iterator end, iterator dest)
往终端上输出
/*
replace 算法 将容器内指定范围的旧元素修改为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param oldvalue 旧元素
@param oldvalue 新元素
*/
replace(iterator beg, iterator end, oldvalue, newvale)
/*
replace_if 算法 将容器内指定范围满足条件的元素替换为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 函数回调或者谓词(返回 Bool 类型的函数对象)
@param oldvalue 新元素
*/
replace_if(iterator beg, iterator end, _callback, newvalue)
/*
swap 算法 互换两个容器的元素
@param c1 容器 1
@param c2 容器 2
*/
swap(container c1, container c2)
/*
accumulate 算法 计算容器元素累计总和
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 累加值
*/
accumulate(iterator beg, iterator end, value)
/*
fill 算法 向容器中添加元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value t 填充元素
*/
fill(iterator beg, iterator end, value)
/*
set_intersection 算法 求两个 set 集合的交集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2,iterator dest)
/*
set_union 算法 求两个 set 集合的并集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/*
set_difference 算法 求两个 set 集合的差集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2,iterator dest)
上一篇:【FPGA-Spirit_V2】小精灵V2开发板初使用
下一篇:TC烟雾报警传感器