我的笔记_《我的第一本C++书》(作者:陈良乔)
第二章
程序的两大任务:描述数据与处理数据。
数据可以看做是对现实世界的各个事物的抽象;对数据的处理就反应了事物的变化,表达了事物之间的关系。对数据处理的抽象,人们称为算法。
iostream 中的输入和输出的意思:
- 输入:数据从外部(包括键盘输入或外部文件)流到程序。有类istream;ifstream
- 输出:数据从程序流动到外部。有类ostream;ofstream
插入符:“<<”;提取符:“>>”
C++中的约定成俗:
- 变量加前缀 s_ ,表示静态(static)变量;变量加前缀 g_ ,表示全局(global)变量;类的数据成员加前缀 m_ ,表示成员(member)变量。
- 常量大写。例如:const int MAX_LENGTH = 100;
字符串类型
3.5.1 字符类型
- char 1个字节(8位或8bit) 范围:-128~127
- signed char 与 char 一样
- unsigned char 1个字节(8位或8bit) 范围:0~255
- wchar_t 2个字节(16位) 为了表示unicode字符
3.7 枚举类型
列出它的所有可能值。枚举类型实质上是整型数值;默认是0,1,2...,也可以指定。
enum Weekday{mon = 1, tue, wed, thu, fri, sat, sun = 0 };//从mon开始,没有指定值的依次加1,最后sun=0 Weekday nDay(tue);//也可以用nDay=tue;初始化 cout << nDay; 结果:2
但,不能用整数给枚举类型变量赋值。枚举类型的数值是常量,定义后不可改变。(不可做lvalue)
最佳实践:不要使用“==”比较两个浮点数是否相等。要用如下方法:
float x = 0.0005; double y = 0.0005; //设定允许的误差值 const double fEpsion = 0.00001; //如果相减的结果,比一极小值还小,则认为相等。 if(fabs(x - y) < fEpsion) cout << "x等于y" << endl; else cout<< "x不等于y" << endl;
const 表示常量:
int* const pNumber = &number;//const在“*”的右边,则表示const修饰的是指针,这个指针的值在声明后不能修改,所以声明时必须赋初值。声明一个整型常量指针 const int* pNumber; // const在“*”的左边,则表示const修饰的是int,这个指针指向的值不能修改。——常量整型指针 int const * pNumber; // 声明一个常量整型指针,意义同上 const int* const pNumber = &number; // 声明一个常量整型常量指针,指针和指针指向的变量值都不能修改。
类的成员函数中的const:
const Stock & Stock::topval (const Stock & s) const { if (s.toltal_val > total_val) return s; else return *this; }
在这个成员函数中,Stock是之前定义的一个类,下面我们介绍每个const的含义。
①const Stock & Stock::topval (②const Stock & s) ③const
我们把三处const分别用序号①②③分别表示,分别讲解。
①处const:确保返回的Stock对象在以后的使用中不能被修改
②处const:确保此方法不修改传递的参数 S
③处const:保证此方法不修改调用它的对象(保证不修改类中的数据成员;函数体中不能调用非const函数)
1.如果函数有返回值,则不可返回一个指向函数体内部声明的局部对象的“指针”或“引用”。
2.简单地用断言(assert)检查参数的有效性。
assert(expression) 的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行
#include <assert.h> //插入断言头文件 using namespace std; double Divide(int nDivident, int nDivisor) { assert(0 != nDivisor); //使用断言判断除数是否为0 return (double)nDivident / nDivisor; } int main() { double fRet = Divide(3, 0); return 0; }
3.函数的功能要单一。
4.函数主体不宜太长。
1.封装:将描述对象属性的数据和描述对象行为或功能的函数(方法)结合在一起,形成对象。——更加准确的描述了现实世界。好处:对数据进行保护,以免外部无意的修改。
2.继承:是可以让某个对象获得另一个类型的对象的属性和方法。好处:代码的重复利用,更方便的维护,更易扩展。
3.多态:相同的调用语句,具有不同的表现形式。多态由继承而来,由虚函数virtual来实现。例如,一个父类的指针指向子类的对象,当调用某一个父类和子类都有的成员函数时,(若用virtual修饰的成员函数)则调用子类自己的成员函数(可能会有许多的子类对象)。若没virtual,则调用父类的方法(成员函数)。
class Father { public: virtual void func(){cout << "调用father的func" << endl;} }; class Son : public Father { public: void func(){cout << "调用Son的func" << endl;} }; int main() { Son son; Father *ptrFuther = &son; ptrFuther->func(); //若Father类中的func函数有virtual修饰,结果为:"调用Son的func" return 0; //若Father类中的func函数没有virtual修饰,结果为:"调用father的func" }
基类的成员在派生类中的访问属性:
可访问性:private < protected < public.
private:只有自己能访问,子类和外部均不可以访问。
protected:自己和自己的子类可以访问,外部不可访问。
public:自己,自己的子类,外部均可以访问。
子类会继承父类全部的成员,继承后子类对父类的成员的访问权限:①原来private升级为不可访问。②在原来的访问属性与继承方式中取可访问性更小的。
子类不可访问父类的所有private;子类可以访问自己的private,protected,public;
操作符重载:
class 类名 { public: 返回值类型 operator 操作符 (参数列表) { ... } };
a + b; 这条语句相当于 a.operator + (b);
1.友元函数:
class 类名 { friend 返回值类型 函数名(形式参数) ; // 友元函数的声明 //类的其他声明和定义... }; 返回值类型 函数名(形式参数) // 友元函数的定义 { ... //可以访问private,protected,public }
友元函数的定义在类的外部,不属于类的成员函数,故其声明既可以在private:,也可以在public:,...,没有区别的。一般单独放。
2.友元类:
class 类名 { friend class 友元类名; // 类的其他声明和定义 }
这样声明后,在友元类中就可以不受类成员访问控制的限制。
class Teacher { friend class TaxationDep; friend int AdjustSalary(Teacher& teacher); public: Teacher():m_nSalary(5000){} //直接定义,隐式的inline函数 private: int m_nSalary ; }; //友元函数 int AdjustSalary(Teacher& teacher) { return teacher.m_nSalary + 299; } //友元类 class TaxationDep { public: int CheckSalary(Teacher& teacher) { return teacher.m_nSalary; } }; int main() { Teacher MrChen; TaxationDep taxOfficial; int nSalary = taxOfficial.CheckSalary(MrChen); cout << nSalary << endl; cout << AdjustSalary(MrChen) << endl; return 0; } //结果 5000 // 5229
函数指针:
函数指针的声明:
函数返回值类型标志符 (指针变量名)(形参列表);
譬如:
-
void (*pPrintFunc)(int nScore);//注意:第一个"()"不可以省略。
-
void (*pPrintFunc)(int);
- typedef void (*PRINTFUNC)(int ); // 如果要定义多个同一类性的指针,还可以用typedef关键字定义一种新的函数指针类型。此句表示定义一种新的函数指针类型PRINTFUNC,它可以指向一个参数为int、返回值为void的函数。用这个类型可以连续定义多个函数指针。
PRINTFUNC pFuncFailed;
PRINTFUNC pFuncPass; ...
- auto pPrintFunc = printPass; //C++11后,auto定义的类型,编译器会在变量赋值的时候,自动推断类型。必须初始化。
函数名就是指向函数的指针,即函数入口的地址。把函数名赋值给函数指针变量即可。
void printPass(int nScore) //函数名就是指向函数的指针,即函数入口的地址。 { cout << "分数是:" << nScore << ",恭喜你通过考试!" << endl; } int main() { int nScore = 72; // 函数指针的声明,本质上就是定义一个指针(像int* p;),只是它指向一个函数。另外,C++11后,可用 auto pPrintFunc = printPass; 代替下面的两行语句。 void (*pPrintFunc)(int nScore);//可以写成省略形式:void (*pPrintFunc)(int ); pPrintFunc = printPass; //用函数名给函数指针赋值 (*pPrintFunc)(nScore); //用函数指针调用函数,实际调用printPass(nScore) return 0; }
既然,调用函数指针与函数的调用没什么差别,那么何必使用函数指针呢?但普通函数的调用不够灵活。指针的灵魂就是它的灵活性。
譬如
void printPass(int nScore) //函数名就是指向函数的指针,即函数入口的地址。 { cout << "分数是:" << nScore << ",恭喜你通过考试!" << endl; } void printFailed(int nScore) { cout << "分数是:" << nScore << ",抱歉,你没有通过考试!" << endl; } void printExcellent(int nScore) { cout << "分数是:" << nScore << ",哇,你是个天才!" << endl; } int main() { int nScore = 172; void (*pPrintFunc)(int ); if(nScore < 60) pPrintFunc = printFailed; else if(nScore >= 60 && nScore < 100) pPrintFunc = printPass; else pPrintFunc = printExcellent; //因为函数指针被不同的函数入口地址赋值,从而实现了不同函数的调用。 (*pPrintFunc)(nScore); //用函数指针调用函数 return 0; }
11.1.3 用函数指针实现回调函数
除了可以使用函数指针简化函数的调用之外,函数指针更大的用途在于它可以作为函数参数传递给某个函数,从而实现函数的回调。譬如:
#include <iostream> using namespace std; void printPass(int nScore) //函数名就是指向函数的指针,即函数入口的地址。 { cout << "分数是:" << nScore << ",恭喜你通过考试!" << endl; } void printFailed(int nScore) { cout << "分数是:" << nScore << ",抱歉,你没有通过考试!" << endl; } void printExcellent(int nScore) { cout << "分数是:" << nScore << ",哇,你是个天才!" << endl; } typedef void (*PRINTFUNC)(int );//定义函数指针类型 void printMessage(int nScore, PRINTFUNC pFunc) { cout << "================" << endl; //通过函数指针回调函数 (*pFunc)(nScore); //这里就像留下一个插口,等待具体的回调函数插头的插入。插口的规则由PRINTFUNC类型规定。 cout << "++++++++++++++++" << endl; } int main() { int nScore = 72; PRINTFUNC pFunc; if(nScore < 60) pFunc = printFailed; else if(nScore >= 60 && nScore < 100) pFunc = printPass; else pFunc = printExcellent; printMessage(nScore, pFunc);//使用不同的函数指针作为参数调用PrintMessage()函数。 return 0; }
11.1.4 将函数指针应用到STL算法中
class Student { public: Student(int height):m_nHeight(height) { } int getHeight() { return m_nHeight; } private: int m_nHeight; }; bool countHeight(Student stu) { return(stu.getHeight() > 170); } int main() { Student student1(163); Student student2(172); Student student3(175); vector<Student> vecStu; vecStu.push_back(student1); vecStu.push_back(student2); vecStu.push_back(student3); //这样,满足countHeight函数条件的进行统计。count_if(begin, end, p)表示:在迭代器[begin, end)中调用if(p(*begin))res++; int nCount = count_if(vecStu.begin(), vecStu.end(), countHeight ); cout << "身高大于170的学生有:" << nCount << endl; return 0; }//结果:身高大于170的学生有:2
为了增加灵活性,把身高标准也作为函数参数。
bool countHeight(int nHeight, Student stu) { return(stu.getHeight() > nHeight); }
然后,这样调用 count_if() 函数(头文件algorithm中)
int nStandardHeight = 170;//定义标准高度 int nCount = count_if(vecStu.begin(), vecStu.end(), bind1st(ptr_fun(countHeight), nStandardHeight) ); //在这里,首先使用 ptr_fun() 函数将一个普通的函数指针转换为一个函数对象, //然后用 bind1st() 函数将整个函数对象的第一个参数绑定为 nStandardHeight ,而第二个参数就是容器中的 Student 对象,即*begin。
除了可以在STL算法中使用指向普通函数指针外,还可以在算法中使用指向某个类的成员函数的函数指针。譬如,用调用成员函数的函数指针的方法来实现上面的例子。
#include <iostream> #include <vector> #include <algorithm>//声明 count_if()函数 using namespace std; class Student { public: Student(int height):m_nHeight(height) { } bool countHeight(int nHeight) { return(m_nHeight > nHeight); } private: int m_nHeight; }; int main() { Student student1(163); Student student2(172); Student student3(175); vector<Student> vecStu; vecStu.push_back(student1); vecStu.push_back(student2); vecStu.push_back(student3); int nStandardHeight = 170;//定义标准高度 int nCount = count_if(vecStu.begin(), vecStu.end(), bind2nd(mem_fun_ref(&Student::countHeight), nStandardHeight) ); cout << "身高大于170的学生有:" << nCount << endl; return 0; }
这里,首先使用“&”运算符获得 Student 类的成员函数 countHeight() 的地址,即指向成员函数的函数指针;然后用mem_fun_ref() 函数将这个函数指针构造成一个函数对象。使用 bind2nd() 函数绑定其第二个参数为nStandardHeight,因为成员函数的隐含默认第一个参数为对象本身,*this
如果容器中保存的是指向对象的指针,就应该用 mem_fun() 函数来完成 bind2nd() 这一语句。
...//同上 int main() { Student student1(163); Student student2(172); Student student3(175); vector<Student*> vecStu; vecStu.push_back(&student1); vecStu.push_back(&student2); vecStu.push_back(&student3); int nStandardHeight = 170;//定义标准高度 int nCount = count_if(vecStu.begin(), vecStu.end(), bind2nd(mem_fun(&Student::countHeight), nStandardHeight) );//此处用mem_fun()来完成 cout << "身高大于170的学生有:" << nCount << endl; return 0; }
总的来说,vector 里放对象用 mem_fun_ref(),vector里放指针用 mem_fun() 。
11.2 函数对象
所谓函数对象,就是定义了函数调用操作符【(function-call operator),即operator() 】 的普通类的对象。简单说,就是重载"()" 的类的对象。
// 类模板 template <class T> class mymax { public: //重载“()”操作符 T operator() (T a, T b) { return a>b ? a:b; } }; int main() { mymax<int> intmax; // 由 模板类 产生 函数对象 int nMax = intmax(3, 4); // 与函数调用极其相似,编译器将 intmax(3,4); 转换为 intmax.operator()(3,4); cout << nMax << endl; // 4 return 0; }
11.2.2 利用函数对象记住状态数据
for_each 的实现:
template<class InputIt, class UnaryFunction> UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; }
譬如:
class Student { public: Student(string name=0, int height=0) : m_strName(name), m_nHeight(height) {} int GetHeight(){return m_nHeight;} private: string m_strName; int m_nHeight; }; //定义一个函数对象类 class AverageHeight { public: AverageHeight() : m_nCount(0), m_nTotalHeight(0) {} //重载"()"操作符 void operator() (Student st) { m_nTotalHeight += st.GetHeight(); ++m_nCount; } //接口函数 float GetAverageHeight() { if(0 != m_nCount) { return (float)GetTotal() / GetCount(); } else return -1; } //获取函数对象类的各个成员属性 int GetCount(){return m_nCount;} int GetTotal(){return m_nTotalHeight;} //定义类型转换函数 /* 类型转换函数的一般形式为 : operator 类型名() {实现转换的语句} */ operator float(){return GetAverageHeight();}//可以将Student类型转换为float类型 private: int m_nCount; int m_nTotalHeight; }; int main() { Student st1("Lee", 165); Student st2("Wang", 168); Student st3("Hou", 171); vector<Student> vecStu = {st1,st2,st3}; //创建函数对象 AverageHeight ah; //将函数对象应用到STL算法中 ah = for_each(vecStu.begin(), vecStu.end(), ah); cout << ah.GetCount() << "个学生的平均身高是:" << ah.GetAverageHeight() << endl; // 168 //若定义类型转换函数,就可以将Student ——> float float average = for_each(vecStu.begin(), vecStu.end(), ah); cout << average << endl; // 168 return 0; }
11.3 Lambda 表达式
在作用上,Lambda 表达式类似于函数指针和函数对象。譬如,上面的例子不用函数对象,而用 Lambda 表达式 则更简单。
int main() { Student st1("Lee", 165); Student st2("Wang", 168); Student st3("Hou", 171); vector<Student> vecStu = {st1,st2,st3}; //定义变量,以保存状态数据 int nTotalHeight = 0; int nCount = 0; //将 Lambda表达式 应用到STL算法中 for_each(vecStu.begin(), vecStu.end(), [&](Student st){nTotalHeight += st.GetHeight();++nCount;}); cout << nCount << "个学生的平均身高是:" << (float)nTotalHeight / nCount << endl; // 168 return 0; }
- Lambda 表达式的语法规则
[ 变量使用说明符 ] ( 参数列表 ) -> 返回值数据类型
{
// 函数体
}
其中,高亮部分可选。中括号 "[ ]" 表示 Lambda 表达式的开始。"[=]" 表示传值(复制)方式,它使得Lambda表达式以只读的方式访问当前作用域的变量。"[ ]" 表示默认方式,也是传值方式。"[&]" 表示以引用的方式定义Lambda表达式,它使得Lambda表达式的变量是外部同名变量的引用,即可以修改外部变量。
vector<int> v = {0,1,2,3}; int nAdd = 3; for_each(v.begin(), v.end(), [=](int x) // [=] 传值方式,但是"[]"方式会出错,不知为什么? { //nAdd = 2; // 试图修改nAdd,编译错误 x += nAdd; //只读访问nAdd cout << x << " "; // 3 4 5 6 });
vector<int> v = {0,1,2,3}; int nAdd = 3; for_each(v.begin(), v.end(), [&](int x) // [&] 引用方式 { nAdd = 2; // 引用方式可以修改外部变量nAdd,编译OK x += nAdd; cout << x << " "; // 2 3 4 5 }); cout << endl << nAdd << endl; // 2 可以看到 nAdd 变了,即实现了向外部传递数据的功能。
如果需要与Lambda表达式传递多个数据,那么可以在"[ ]" 的第一位设置一个默认的传递方式,然后再分别指定各个变量的传递方式。譬如
vector<int> v = {0,1,2,3}; int nAdd = 3; int nTotal = 0; for_each(v.begin(), v.end(), [&, nAdd](int x) // 默认情况子下使用引用方式,nAdd使用传值方式 { nTotal += (x*nAdd); //只读访问nAdd }); cout << "容器中的数据乘以" << nAdd << "之后总和是" << nTotal << endl;
通常来说,Lambda 表达式没有返回值,这时可以省略返回值类型的定义,若某些算法需要它有返回值,则用“->”来定义返回值类型。譬如
vector<int> v = {0,1,2,3,}; int nEven = count_if(v.begin(), v.end(), [=](int x) -> bool // 也可以 [](int x) 或者 [](int x) -> bool 都行 { return x%2==0; }); cout << nEven << endl; // 2
11.3.3 Lambda 表达式的复用
vector<int> v = {0,1,2,3,}; list<int> l = {2, 4, 6 ,8}; l.push_back(10); //定义一个可以输出整数的Lambda表达式 auto show = [](int x) { cout << x << endl; }; //注意有";" //在vector容器上应用 for_each(v.begin(), v.end(), show); //在list容器上应用 for_each(l.begin(), l.end(), show);
12.1 右值
左值:可以放在“=”的左边,即能被赋值也能对其他左值赋值。对左值的引用:“&”
右值:只能放在“=”的右边,给左值赋值。通常是一些数值常量、临时变量或无名变量等,例如一个函数的返回值。对右值的引用:“&&”
//这个函数返回值就是右值 int creatInt(int x) { return x; } int main() { int i;// 定义一个int类型变量,这个变量就是左值 //定义一个左值引用,左值引用只能绑定左值 int& lrefInt = i; //定义一个右值引用,右值引用只能绑定右值(临时对象) int&& rrefInt = creatInt(4); //左值引用与右值引用的使用没有区别,都可以当成普通数据类型变量 rrefInt = 1; lrefInt = rrefInt; return 0; }
实际上,右值就是一些无名的数据变量。
右值引用在函数返回值上的应用。
//利用函数创建并返回一个类的对象 MemoryBlock CreateBlock(size_t nSize) { return MemoryBlock(nSize); } //利用返回值对变量进行赋值 MemoryBlock block = CreateBlock(703); MemoryBlock&& block2 = CreateBlock(703);
12.2 智能指针 shared_ptr
#include <iostream> #include <memory>//声明shared_ptr using namespace std; int main() { shared_ptr<int> pFirst(new int); //这时,只有一个指针指向这块int类型的内存,所以这时的引用计数是1。 cout<< "当前引用计数:" << pFirst.use_count() << endl; { //创建另一个shared_ptr,并用pFirst对其赋值,让它们指向同一块内存资源 shared_ptr<int> pCopy = pFirst; //这时,引用计数是2,pCopy.use_count()也是2 cout << "当前引用计数:" << pFirst.use_count() << endl; } //pCopy的生命周期结束,引用计数减1 cout<< "当前引用计数:" << pFirst.use_count() << endl; //当程序结束执行返回,pFirst指针也结束其生命周期 //这时引用计数为0,内存资源自动得到释放 return 0; }
并不是任何时候都应该使用智能指针。
总结起来,以下情况应该优先考虑使用 share_ptr
- 有多个使用者共同使用同一对象,而没有一个明显的拥有者。
- 一个对象的复制操作很昂贵。
- 要把指针存入标准库容器。
- 要传送对象到库或从库获取对象,而这些对象没有明确的所有权。
- 当管理需要特殊清理方式的资源时,这时可以通过定制shared_ptr 的删除器来实现。
#include <iostream> #include <memory>//声明shared_ptr using namespace std; class Employee { public: Employee(string strName) : m_strName(strName){} string GetName(){return m_strName;} private: string m_strName; }; class PrintEng { public: PrintEng(shared_ptr<Employee> sp) : m_spEmp(sp) {} void doPrint() { if(true == (bool)m_spEmp) cout << "Name of Employee: " << m_spEmp->GetName() << endl; } private: shared_ptr<Employee> m_spEmp; }; class PrintChs { public: PrintChs(shared_ptr<Employee> sp) : m_spEmp(sp) {} void doPrint() { if(true == (bool)m_spEmp) cout << "员工的姓名:" << m_spEmp->GetName() << endl; } private: shared_ptr<Employee> m_spEmp; }; int main() { shared_ptr<Employee> spEmp(new Employee("JiaWei")); //将智能指针spEmp共享给pEng对象 PrintEng pEng(spEmp); pEng.doPrint(); //将智能指针spEmp共享给pChs对象 PrintChs pChs(spEmp); pChs.doPrint(); //不用自己释放内存空间,智能指针帮忙释放了。 return 0; }
瘦身版的智能指针 —— unique_ptr
由于shared_ptr指针需要40字节的内存,体积较大。故有 unique_ptr,当 unique_ptr 销毁时,同样会自动释放它所管理的内存空间。与 shared_ptr 不同的是,某个内存资源只允许一个 unique_ptr 与之关联,对其进行管理。也就是说 unique_ptr 不能进行复制。
//定义一个unique_ptr,并将其与一个Employee对象关联 unique_ptr<Employee> upEmployee(new Employee); //使用 -> int nAge = upEmployee->GetAge(); //使用 * PrintObj(*upEmployee);
13.1.2 函数
题8 请编写一个函数将一个链表翻转。例如,现在有一链表1->2->3->4->5,通过调用函数将链表翻转成为5->4->3->2->1。
Student* reverseList(Student* head) { if(NULL == head) //对当前的参数有效性进行验证 return head; Student *pre, *cur, *next; //定义前一节点、当前节点、后一节点 pre = head; cur = pre->next; while(cur) { next = cur->next; cur->next = pre; pre = cur; cur = next; } head->next = NULL; head = pre; return head; }
或者,用指针的引用
struct link { int data; link* next; }; //反转链表函数 void reverseList(link* &head) { if(NULL == head) return ; Student *pre, *cur, *next; pre = head; cur = pre->next; while(cur) { next = cur->next; cur->next = pre; pre = cur; cur = next; } head->next = NULL; head = pre; }
题 9 请编写一个字符串拷贝函数
//糟糕的 void strcpy( char* strDest, char* strSrc ) { while( (*strDest++ = *strSrc++) != '\0' ); }
比较完美的答案:
// 为了实现链式操作,将目标地址返回 char* strcpy ( char* strDest, const char* strSrc) { assert( (strDest != NULL) && (strSrc != NULL) ); //断言是否为空指针 //保存目标地址 char* address = strDest; //逐个复制字符串数组中的数据,直到字符串结束 while( (*strDest++ = *strSrc++) != '\0' ); return address; }
题 10 内联函数的意义
编译时,内联函数的代码会被插入代码流中,避免了函数的调用,从而改善了程序的性能。另外,将一个函数定义为内联函数,有些要使用inline关键字在函数名前,有些则不需要,例如直接在类定义中定义成员函数。内联函数只是给编译器一个“建议”,编译器可以采纳,也可以忽略。
13.1.3 面向对象思想
面向对象思想是开发大型的复杂的应用软件和系统的最佳方法,了解它,有助于学习和应用面向对象技术。
题 12 面向对象的三个基本特征
1. 封装:封装将客观事物抽象成类,每个类对自身的数据和方法实行访问控制,通过关键字public、protected和private 来控制外界对类成员的访问,以达到保护数据和方法的目的。
2. 继承:子类从父类继承,从而获得父类的所有属性和方法。
3. 多态:相同的调用语句,不同的表现形式。由虚函数来实现,简单说,允许子类指针赋值给父类指针,并且用指向子类对象的父类指针可调用子类自己的函数。
题 13 何时该使用继承
当某个事物是另一个事物的“一种”时(即 is-a 关系),就应该使用继承。
题 14 类是什么?对象又是什么?
类是对现实世界中同一类物体的抽象,它是概念、规范。它包括成员变量和成员函数(即属性和方法)。
对象是类的实例化,是具体的。例如,int i,i 就是 int 类型的对象。
题 15 简述struct 与 class 的区别
在语法上,struct 与 class 的唯一区别就是默认访问权限。struct 默认成员是公有的,默认继承方式是公有;而class 默认成员是私有的,默认继承也是私有的。一般来说,当类有很少是方法并且有公有的数据时,才用struct,否则使用class。
题 16 重载(overload)和重写(override)的区别
重载:是指在同一作用域允许同时存在多个同名函数,但它们之间的函数参数表不同。另外,函数的返回类型不同,不能构成函数重载。const 限定参数按值传递不能构成重载,而const限定参数按指针或引用传递时,则可以构成重载。const 限定成员函数 也可构成重载。
重写(覆盖):是指子类重新定义父类的方法。其参数列表、返回类型必须与父类的一致。像重写虚函数以实现多态。注:C++中,只有对virtual函数才是重写。非virtual函数的重新定义也可以,其成为重定义或隐藏。
class Base { public: //C++11允许将方法标记为final,这意味着无法在子类中重写这个方法。试图重写final()方法将导致编译器错误。 virtual void virFunc()/*final*/ //虚函数,子类中此函数默认virtual,可写可不写。 {cout << "Base virFunc()" << endl;} void func() { cout << "Base func()" << endl; } }; class Sub : public Base { public: virtual void virFunc(){cout << "Sub virFunc()" << endl;} // 基类中virFunc有virtual,故是重写或覆盖。 void func(){cout << "Sub func()" << endl;} // 基类中func无virtual,重定义 }; int main() { Sub a; a.func(); // Sub func() a.Base::func(); // Base func() a.virFunc(); // Sub virFunc() a.Base::virFunc(); // Base virFunc() // 重写与重定义的区别主要在多态上 Base* p; p = &a; //父类指针指向子类对象 p->func(); // Base func() 非虚函数调用父类的 p->virFunc(); // Sub virFunc() 虚函数调用自己的 return 0; }
题 17 子类覆盖父类的虚函数是否不用加virtual 关键字?
virtual修饰符是会隐性继承的,子类中虚函数前virtual可加可不加,但为了增加代码的可读性,最好还是加上。
题18 能重载类的析构函数吗?
不能,在C++中,类有且只有一个析构函数。无论何时都不能传递参数给析构函数,也就无法根据参数的变化形成重载。
题19 局部对象的析构的顺序是什么?
局部对象的析构是反序的,就是说,先构造的对象,后析构。
题20 在派生类的析构函数中,需要显示的调用基类的析构函数吗?
永远不需要显示地调用析构函数,在派生类的析构函数当然也不需要。派生类的析构函数(无论是否显式定义)会自动调用基类的析构函数,并且基类的析构函数在派生类的对象析构之后调用。
题21 可以将一个派生类指针转换为它的基类指针吗?
可以。派生类是基类的一种,所以从派生类指针到基类的指针转换是非常安全的,并且始终成功。经常使用。
题22 C++中是如何实现指针的静态类型和动态绑定的?
有一个父类指针,它指向子类的对象。像 Student st; Human* pHuman = &st; ,这时,pHuman指针就有两种类型:指针的静态类型(此处是Human);它所指向的动态类型(此处是Student)。静态类型,使得编译器在编译时能够检查成员函数调用的合法性。动态绑定,在编译时成员函数的调用并不确定,而是在运行时根据指针指向的对象来确定调用哪个函数。动态绑定是虚函数所带来的C++特性之一。
题23 在C++中,如何实现接口与实现的分离?
在C++语言中,我们是通过抽象基类来实现接口与实现的分离的。我们把带有一个或多个纯虚成员函数的类称为抽象基类。它不能实例化,在子类中实现抽象基类的纯虚函数,就是将接口逐个实现。
题24 如何将自定义的类通过标准输出流对象输出?
通过重载“<<”运算符,自定义的类也可以通过标准输出流对象输出。
class Student { friend ostream& operator<< (ostream& o, const Student& st); friend istream& operator>> (istream& i, Student& st); private: string m_strName; }; ostream& operator<< (ostream& o, const Student& st) { return o << st.m_strName; } istream& operator>> (istream& i, Student& st) { return i >> st.m_strName; } int main() { Student stChen; cin >> stChen; // Chen cout << stChen << endl; // Chen return 0; }
题 25 完成自己的String类
//自己的String类 class String { public: String(const char* data = NULL) ; String(const String& str); ~String(); String& operator=(const String& str); String& operator=(const char* str); friend String operator+(const String& str1, const String& str2); friend ostream& operator<< (ostream& o, const String& str); private: char* m_data; }; String::String(const char* data ) { if(NULL == data) { m_data = new char[1]; m_data[0] = '\0'; } else { m_data = new char[strlen(data) + 1]; strcpy(m_data, data); } } String::String(const String& str) { m_data = new char [strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); } String::~String() { delete[] m_data; } String& String::operator=(const String& str) { m_data = new char[strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); return *this; } String& String::operator=(const char* str) { m_data = new char[strlen(str) + 1]; strcpy(m_data, str); return *this; } String operator+(const String& str1, const String& str2) { char* buffer = new char[ strlen(str1.m_data) + strlen(str2.m_data) +1 ]; strcpy(buffer, str1.m_data); strcat(buffer, str2.m_data); String newStr(buffer); delete[] buffer; return newStr; } ostream& operator<< (ostream& o, const String& str) { return o << str.m_data; }
题 26 如何删除容器中的元素
typedef vector<int> IntArray; IntArray arr; arr.push_back(1); arr.push_back(2); arr.push_back(2); arr.push_back(3); //删除所有的数字2 //方法1:遍历 for(IntArray::iterator it = arr.begin(); it != arr.end(); ++it) { if(2 == *it) { arr.erase(it); --it; } } // 方法2:用remove() 在<algorithm>中 arr.erase(remove(arr.begin(),arr.end(),2), arr.end());
题 27 一个班级的成绩保存在vecSorce 容器中,请统计其中的及格人数。
vector<int> vecSorce = {35, 60, 65, 75, 86, 90}; int nPass = 0; //方法1:遍历 for (auto it = vecSorce.begin(); it != vecSorce.end(); ++it) { if(*it >= 60) nPass++; } //方法2:for_each() <algorithm> for_each(vecSorce.begin(), vecSorce.end(), [&](int nSorce) // []中有&,则可以修改nPass。若无,则不可以修改nPass。 { if(nSorce >= 60) nPass++; }); //方法3:count_if() <algorithm> //nPass = count_if(vecSorce.begin(), vecSorce.end(), bind2nd(std::greater<int>(), 60)); // *it > 60 nPass = count_if(vecSorce.begin(), vecSorce.end(), not1(bind1st(std::greater<int>(), 60))); // !(60 > *it) cout << "及格人数:" << nPass << endl;
题 31 float 与 double 该如何选择
若精度要求不太高,可以用 float;若精度要求高,精度非常重要,则选择double。因为float 只保证小数点后6位的正确性;而double 可到达小数点后15位。
题 33 求下面函数的返回值
int func(int x) { int countx = 0; while(x) { coutx ++; x = x & (x-1); } return countx; } 假定 x = 9999。 答案是:8。其实是将 x 转换为2进制数,其中含1 的个数。
题 34 写一个函数寻找整数数组中的第二大的数
int find_sec_max(int data[], int n) { int maxNum = data[0]; int secMax = INT_MIN; for (int i = 1; i < n; ++i) { if(data[i] > maxNum) { secMax = maxNum; maxNum = data[i]; } else { if(data[i] > secMax && data[i] < maxNum) { secMax = data[i]; } } } return secMax; }
6.3.3 用虚函数实现多态
如果通过基类指针调用虚函数,那么将调用这个指针所指向的具体对象的虚函数,以此来代替基类的虚函数。
若有virtual,自己有调用自己的,自己没有调用父类的。注意:类声明外部不可以使用 virtual,即虚函数外部实现时不要带 virtual 修饰。
class Human { public: //注:基类的函数是虚函数(声明前加virtual), // 那么派生类的此函数都是虚函数。前加不加virtual都可以,一般加上。 virtual void BuyTicket() { cout << "人买票\n"; } virtual ~Human(){} }; class Teacher : public Human { public: void BuyTicket() { cout << "老师投币买票\n"; } }; class Student : public Human { public: void BuyTicket() { cout << "学生刷卡买票\n"; } }; int main() { //声明一个基类的指针 Human* p = NULL; //车上上来一位老师 p = new Teacher(); //老师买票 p->BuyTicket(); delete p; //车上上来一位学生 p = new Student(); //学生买票 p->BuyTicket(); delete p; p = NULL;//栓住指针以防乱指。 return 0; } 结果: 老师投币买票 学生刷卡买票 注:若无virtual,则是 人买票 人买票
若想强制派生类定义某个函数,则可以在基类中将这个函数声明为纯虚函数,也就是基类不实现这个虚函数,它的所有实现都留给派生类来完成。
class Human { public: virtual void BuyTicket() = 0;//纯虚函数 virtual ~Human(){} };
当类中有纯虚函数时,这个类就成为了一个抽象类。不能创建抽象类的具体对象,因为有尚未完工的纯虚函数。
Human ren;//编译会出错,因为不可实例化抽象类。
若从抽象类派生某个类,那么它必须实现其中的虚函数才能成为一个实体类。否则还是抽象类。
6.5.1 C++ 类对象的内存模型
对象的第一个成员变量的地址跟整个对象的地址相同,第二个成员变量紧跟其后。对象中的成员变量是按照类声明中的顺序依次排列的。
而类的成员函数都被放在一个特殊的位置(因为同一类的所有对象的成员函数都是相同的,没有必要为每个对象配备一份),所有这个类的对象都共用这份成员函数。如下图:
另外:若类中有虚函数,那么在对象最开始的内存位置添加一个虚函数表的指针 _vfptr ,其后才是对象的成员变量内存数据。若某个类是派生类,那么它的对象内存中最开始的地方其实是基类的拷贝(包括基类的虚函数表指针和成员变量),其后才是派生类自己的成员变量数据。
6.5.2 指向自身的this指针
this指针是指向当前对象的指针。(即当前对象的地址)。每个非静态成员函数的第一个参数总是this指针(被系统隐式的传递)。
7.1.2 灵活的 void 类型和 void 类型指针
在程序中,void类型更多的是用于“修饰”和“限制”一个函数。例如:如果一个函数没有返回值,则用void作为这个函数的返回值类型;如果一个函数没有形式参数,则可用void作为其形式参数,表示这个函数不需要任何参数。
跟void类型不同,void类型指针作为指向抽象数据的指针,它可以成为两个具有特定类型指针之间相互转换的桥梁。任何其他类型的指针都可以直接赋值给void类型指针,但void类型指针必须强制类型转换为其他指针。因为“无类型”可以包容“有类型”,而“有类型”不能包容“无类型”。
int* pInt; float* pFloat; void* pVoid; pVoid = pInt; // 其他指针类型 ---》 void* 可以直接赋值 pFloat = (float*)pVoid; // void* ---》 其他指针类型要强制类型转换 //另外,C++引进了新的类型转换操作符 static_cast 。形式: static_cast<类型说明符>(表达式)。 //譬如:上面的语句可写为 pFloat = static_cast<float*>(pVoid);
当然,如果把void类型指针转换为并不是他实际指向的数据类型,其结果是不可预测的。
若函数可以接受任何类型的指针,那么应该将其参数声明为 void*。
譬如:内存复制函数: void * memcpy(void *dest, const void *src, size_t len);
7.1.4 指针在函数中的应用
- 指针作为函数参数
可以不用大量数据的拷贝,又可以对同一数据进行读/写操作。因为函数的调用者和函数都可以使用指向同一内存地址的指针。
- 指针作为函数的返回值
牢记:指针函数可以返回全新申请的内存地址;可以返回全局变量的地址;可以返回静态变量的地址,但就是不可以返回局部变量的地址。(因为函数内部声明的局部变量在函数结束后,其生命周期已结束,内存会被自动释放。)
7.3.2 名字空间
不同的名字空间下,可以有相同的函数、数据声明。定义一个名字空间的语法格式:
namespace 名字空间名 { //名字空间内的声明与定义 } //结尾可以加';'
若没有说明在哪一个具体的名字空间,则默认在全局名字空间(又称匿名名字空间)
- 如何使用名字空间:
具体的名字空间要用 名字空间名::数据类型(类) 变量名
全局名字空间用 ::数据类型(类) 变量名
通常用 “ using namespace ” 关键字来指明编译时默认查找的名字空间。但不能与默认使用的全局名字空间中的数据类型相同。譬如:
#include <iostream> namespace Zhangsan { struct Student { int nAge; }; } struct Student { bool bMale; }; using namespace std; //将std空间,作为编译时默认查找的名字空间 using namespace Zhangsan; //将Zhangsan空间,作为编译时默认查找的名字空间 //注意:全局的名字空间,默认查找。 int main() { Student stu1; //编译错误。“'Student' is ambiguous”,由于Zhangsan空间与全局名字空间都有成员Student ::Student stu2; //Ok;使用全局名字空间的Student Zhangsan::Student stu3; //OK;使用Zhangsan空间的Student return 0; }
若成员名(类名或函数名,变量名...)前不用 作用域分解符"::" ,则必须保证默认查找的名字空间们中只有一个这样的名称。否则,必须显式地使用 "::"。
- extern
若想在多个源文件中使用某个源文件定义的全局变量或函数,则在多个源文件中 extern int gTotal; extern int Add(int , int ); 来重新声明全局变量和全局函数。
使用通用算法 count_if 统计容器中大于100的元素个数
#include <iostream> #include <vector> #include <algorithm> //std::count_if using namespace std; //将std空间,作为编译时默认查找的名字空间 int main() { vector<int> v; for(int i = 0; i < 10; ++i) { v.push_back(i+93); } for(int& k : v) { cout << k << " "; } cout << endl; //使用通用算法 count_if 统计容器中大于100的元素个数 int nTotal = count_if( v.begin(), v.end(), bind2nd(greater<int>(), 100)); cout << nTotal << endl; //降序排序 sort (v.begin(), v.end(), greater<int>()); for(int& k : v) { cout << k << " "; } cout << endl; return 0; }
第八章 用STL优雅你的程序
STL(Standard Template Library) = algorithm + container + iterator.
8.2.1 函数模板
函数模板代表一类函数。(可理解为:产生函数的模板)
#include <iostream> using namespace std; //函数模板的定义 template <class T> //此处 class 与 typename 等价 T mymax(const T &a, const T &b) { return a > b ? a : b; } int main() { int nA = 2; int nB = 5; cout << mymax(nA, nB) << endl; //动态生成模板函数int mymax(int, int) cout << mymax<int>(nA, nB) << endl; //显式调用模板函数 float fA = 2.3; float fB = 2.5; cout << mymax(fA, fB) << endl;//动态生成模板函数float mymax(float, float) cout << mymax<float>(fA, fB) << endl;//显式调用模板函数 //cout << mymax("Chen", "Jia") << endl;//编译错误,因为"Chen"的类型是const char [5],而"Jia"是const char [4]。默认字符串常量后有'\0'。 //cout << mymax("Che", "Jia") << endl;// 编译错误 cout << mymax<string>("Chen", "Jia") << endl;//OK,比较大小以字典顺序 return 0; }
模板特化。有了某个特定类型的模板特化之后,当使用这一类型的模板函数时,编译器将使用特化后的模板函数,而其他类型,仍将用模板函数的普通版本。
//模板特化 template <> string mymax(const string& a, const string& b) //参数列表把T替换,其他都要一致。 { return a.length() > b.length() ? a : b; }
一般
//较小值 template <class T> inline const T& Min(const T& a, const T& b) { return b < a ? b : a; } //较大值 template <class T> inline const T& Max(const T& a, const T& b) { return a < b ? b : a; }
8.2.2 类模板
#include <iostream> //#include <vector> //#include <algorithm> using namespace std; //类模板的定义 template <typename T> //此处 typename 与 class 等价 class compare { public: //构造函数,实际上它相当于一个函数模板 compare(T a, T b) : m_a(a),m_b(b) { } //类模板中的函数都类似于函数模板 const T& Min() const // 若想用 T& ,后边加了const,前面也要加const,否则错误 { return m_a < m_b ? m_a : m_b; } const T& Max() const { return m_a > m_b ? m_a : m_b; } private: T m_a; T m_b; }; int main() { //类模板的实例化,就是类,即模板类(由类模板产生的类) compare<int> intcompare(2,3); cout << intcompare.Max() << " > " << intcompare.Min() << endl; compare<string> stringcompare("A","a"); cout << stringcompare.Max() << " > " << stringcompare.Min() << endl; return 0; }
泛型编程(generic programming)就是一种大量应用模板来实现更好代码重用性的编程方式。
容器(container)
顺序容器:vector(向量)、list(线性表)、双向队列(deque)、队列(queue)...
关联容器:它所容纳的对象是由 {键-值} 对组成,有set(集合),map(映射)...
连续内存容器:vector
基于节点的容器:list
迭代器
C++ 语言中的指针可以看成是一种迭代器,但迭代器不仅仅是指针。
//使用迭代器循环遍历容器中的数据 for ( vector<int>::iterator it = vect1.begin(); it != vect1.end(); ++ it) { nTotal += (*it); } //注:一般使用 != 来判断是否到达循环结束位置,而不用 < ,因为在某些容器中没有定义 < 号
- 一般来说,容器可以存放普通数据,对象,也可以存放这些对象的指针。如果使用的是基于连续内存的容器(像vector),当在这些容器中插入或者删除元素时,往往会引起内存的重新分配或内存的复制移动。在这种情况下,我们优先选择保存对象的指针(因为指针的体积通常比对象的更小)。对基于节点内存的容器,一般选择保存对象。如果需要保存一些机器资源(例如,文件句柄、命名管道、套接字),那么通常选择保存对象的指针。
- 若容器里存的是 new 出来的指针,必须手动释放内存。
vector<Employee*> vecEmployee; //对容器进行操作... //使用完之后,释放指针所指的内存;清空容器。 for (auto it = vecEmployee.begin(); it != vecEmployee.end(); ++it) { delete (*it); //释放指针指向的对象 *it = NULL; } vecEmployee.clear(); // 清空整个容器
STL为我们引荐了一位打包专家——tuple。可以代替一些简单的结构体。
#include <iostream> #include <vector> #include <tuple> using namespace std; int main() { //tuple<string, unsigned int, double>已经是一个新的类型,分别表示姓名,年龄,体重 tuple<string, unsigned int, double> huChen; //使用make_tuple()函数对huChen赋值 huChen = make_tuple("Chenliangqiao", 28, 66.3); //或者更简单的,利用typedef为这种新的数据类型定义一个简短的类型名 typedef tuple<string, unsigned int, double> Human; //利用tuple的构造函数为变量赋初值 Human huJia("Jiawei", 23, 56.3); vector<Human> vecHuman; vecHuman.push_back(huChen); vecHuman.push_back(huJia); //获取tuple数据变量中的数据 cout << "姓名:" << get<0>(huChen) << endl; get<1>(huChen) ++; cout << "年龄:" << get<1>(huChen) << endl; cout << "体重:" << get<2>(huChen) << endl; //用tie()函数将多个变量捆绑在一起,接受tuple数据组变量赋值 string strName; unsigned nAge; double fWeight; tie(strName, nAge, fWeight) = huChen; cout << "姓名:" << strName << endl; cout << "年龄:" << nAge << endl; cout << "体重:" << fWeight << endl; return 0; }
还有一种打包两种数据的方法——pair
//头文件:#include <utility>,但我没加依然正确,?? //pair的声明并初始化 pair<int, string> a(201610228, "lee"); cout << a.first << " " << a.second << endl; //也可以用make_pair()函数初始化 pair<int, char> b; b = make_pair(12720510, 'L'); cout << b.first << " " << b.second << endl; //用复制构造函数初始化 pair<int, string> c(a); cout << c.first << " " << c.second << endl; //甚至,C++11之后,可以用auto auto d = make_pair("I love ", "you!"); cout << d.first << " " << d.second << endl;
- map
#include <iostream> #include <map> using namespace std; class Employee { public: Employee(){} Employee(int a, string b) : nAge(a),strName(b){} int nAge; string strName; }; int main() { map<int, Employee> mapEmployee; //注:此处要求Employee必须有默认构造函数,即Employee(){} Employee emp1(15, "Lee"); Employee emp2(25, "L"); //使用pair的模板类的对象,插入map容器中 mapEmployee.insert(pair<int, Employee>(1,emp1)); //或使用value_type类型实现数据的插入 mapEmployee.insert(map<int, Employee>::value_type(1, emp2));//由于键值一样,插入失败,但没有错,只是相当于没有插入。 //或使用“[]” mapEmployee[1983] = emp1; mapEmployee[2] =emp2; //以上三种方法是等效的。 //因为map对所用的键进行排序,所用键必须是唯一的,必须能比较大小,对基本类型不必担心,但自定义类型就需要重载"<"运算符。 cout << mapEmployee.size() << endl; //利用迭代器访问map容器中的数据 for ( map<int, Employee>::iterator it = mapEmployee.begin(); it != mapEmployee.end(); ++it ) { cout << "当前员工工号是:" << it->first << endl; cout << "姓名:" << it->second .strName << endl; } //定义要查找的键 int nFindKey = 3; //使用find()函数查找键,返回指向这个键的数据对的迭代器。若无,返回末尾迭代器end() map<int, Employee>::iterator it = mapEmployee.find(nFindKey); if (mapEmployee.end() == it) { cout << "无法找到键为" << nFindKey << "的数据对。" << endl; } else { cout << "找到键为" << nFindKey << "的数据对。" << endl; cout << "年龄:" << it->second.nAge << endl; cout << "姓名:" << it->second.strName << endl; } //定义键的范围 int nFromKey = 1; int nToKey = 1000; //用迭代器表示起始位置和终止位置 map<int, Employee>::iterator itform = mapEmployee. lower_bound( nFromKey );//返回第一个 <= nFromKey 的迭代器 map<int, Employee>::iterator itto = mapEmployee. upper_bound( nToKey );//返回第一个 > nToKey 的迭代器,若没找到,返回last //判断是否在正确的范围 if(mapEmployee.end() != itform && mapEmployee.end() != itto ) { cout << "正确范围。" << endl; } else { cout << "错误范围。" << endl; } return 0; }
10.3 容器元素的复制与变换
10.3.1 复制容器元素:copy() 与 copy_if()
template< class InputIt, class OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt d_first ); template< class InputIt, class OutputIt, class UnaryPredicate > OutputIt copy_if( InputIt first, InputIt last, OutputIt d_first, //C++11之后 UnaryPredicate pred );
实例:
vector<int> vecScoreC1 = {65, 59, 85, 92, 25,}; vector<int> vecScoreC2 = {60, 95, 46, 86, 35,}; vector<int> vecScore; vecScore.resize(vecScoreC1.size() + vecScoreC2.size()); //将第一个容器vecScoreC1中的数据复制到vecScore中 vector<int>::iterator lastit = copy(vecScoreC1.begin(),vecScoreC1.end(), vecScore.begin());//返回复制进来的新数据的尾迭代器 //将第二个容器vectScoreC2中的数据复制到 copy(vecScoreC2.begin(), vecScoreC2.end(), lastit); for (int& k : vecScore) { cout << k << " "; } cout << endl; vector<int> vecScore2(10);//必须分配足够的空间,现在数组中是10个0。 auto lastit2 = copy_if(vecScoreC1.begin(), vecScoreC1.end(), vecScore2.begin(), bind2nd(greater<int>(), 60));//复制大于60的元素 copy_if(vecScoreC2.begin(), vecScoreC2.end(), lastit2, bind2nd(greater<int>(), 60));//复制大于60的元素 for (int& k : vecScore2) { cout << k << " "; } cout << endl;
- copy_backward() 函数
template< class BidirIt1, class BidirIt2 > BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last ); //从 [first, last) 中的最后一个元素开始拷贝到指定位置d_last,依次往前放。返回最后复制的元素的迭代器。 //个人理解:把 [first, last) 的元素(顺序不变地)复制到指定末尾元素位置的地方。返回整个复制块的头迭代器。 // 像 将{65, 59, 85, 92, 25} copy_backward 到 {0,0,0,0,0,0,0,0,0,0} 的end()位置 ----> {0,0,0,0,0,65, 59, 85, 92, 25} 返回的迭代器指向65
譬如:
vector<int> vecScoreC1 = {65, 59, 85, 92, 25,}; vector<int> vecScore(10); //copy_backward()从最后一个元素开始拷贝到指定位置往前放 vector<int>::iterator lastit = copy_backward(vecScoreC1.begin(),vecScoreC1.end(), vecScore.end());//返回复制进来的最后元素的迭代器,即指向65的迭代器 cout << *lastit << endl; // 65 copy(vecScore.begin(),vecScore.end(),ostream_iterator<int>(cout," "));//在<iterator>头文件中,相当于cout<<... cout << endl; // 0 0 0 0 0 65 59 85 92 25
10.3.2 合并容器元素:merge()
template <class InputIterator1, class InputIterrator2, class OutputIterator> OutputIterator merge ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result );//默认从小到大,若想从大到小,最后加一个参数 comp 比较函数。 // 将两个容器的范围 [first1, last1), [first2, last2),而result表示合并到目标容器的起始位置。使用 merge() 可以将两个排序后的容器合并成一个新的有序容器。返回尾迭代器。
使用方法:
升序:
vector<int> vecScoreC1 = {65, 59, 85, 92, 25,}; vector<int> vecScoreC2 = {5, 9, 75, 2, -2,}; //使用 merge() 进行合并之前,必须先使用 sort() 对两个容器排序。 vector<int> vecScore; vecScore.resize(vecScoreC1.size() + vecScoreC2.size());//分配足够的空间 sort(vecScoreC1.begin(), vecScoreC1.end()); sort(vecScoreC2.begin(), vecScoreC2.end()); merge(vecScoreC1.begin(), vecScoreC1.end(), //第一个容器范围 vecScoreC2.begin(), vecScoreC2.end(), //第二个容器范围 vecScore.begin()); //目标容器的开始位置 copy(vecScore.begin(), vecScore.end(), ostream_iterator<int>(cout, " ")); cout << endl; // 结果:-2 2 5 9 25 59 65 75 85 92
降序:
vector<int> vecScoreC1 = {65, 59, 85, 92, 25,}; vector<int> vecScoreC2 = {5, 9, 75, 2, -2,}; //使用 merge() 进行合并之前,必须先使用 sort() 对两个容器排序。 vector<int> vecScore; vecScore.resize(vecScoreC1.size() + vecScoreC2.size());//分配足够的空间 sort(vecScoreC1.begin(), vecScoreC1.end(), greater<int>()); sort(vecScoreC2.begin(), vecScoreC2.end(), greater<int>()); merge(vecScoreC1.begin(), vecScoreC1.end(), //第一个容器范围 vecScoreC2.begin(), vecScoreC2.end(), //第二个容器范围 vecScore.begin(), greater<int>()); //目标容器的开始位置 copy(vecScore.begin(), vecScore.end(), ostream_iterator<int>(cout, " ")); cout << endl; // 92 85 75 65 59 25 9 5 2 -2 //试了一下,没有 sort 也可以,但只是简单的拼接(不排序),也不成块,但merge降序会成块。 //还有若sort排序与merge排序不一致,也是简单的拼接并不排序。 //若默认升序,则sort与merge都默认。若想降序,则sort与merge均降序。可以先简单地拼接,然后在sort排序。
set_union() 将在合并时相同的元素只保留一份。set_union()返回尾迭代器。实际上,set_union() 是计算两个容器的并集。set_deference() 计算两个容器的差集。譬如:
vector<string> a = {"Pen", "Eraser", "Notebook",}; vector<string> b = {"Pen", "Folder", "Pen"}; vector<string> c; c.resize(a.size() + b.size());//分配足够的空间 sort(a.begin(), a.end()); sort(b.begin(), b.end()); auto it = set_union(a.begin(), a.end(), //第一个容器范围 b.begin(), b.end(), //第二个容器范围 c.begin()); //目标容器的开始位置,也可以加comp比较函数。 cout << *(it-1) << endl; // set_union()返回尾迭代器 copy(c.begin(), c.end(), ostream_iterator<string>(cout, " ")); cout << endl; // Eraser Folder Notebook Pen Pen //看出set_union()可以合并两个容器并把它们之间相同的元素只保留一个, //但对容器内部的相同元素无能为力。若想删除容器中的重复元素,可以先sort,再unique,后erase。 //sort(v.begin(),v.end()); v.erase(unique(v.begin(), v.end()), v.end());
10.3.3 变换容器元素:transform 函数
transform() 原型如下:返回尾迭代器(目标容器中被存入最后一个元素的下一位置)
template< class InputIt, class OutputIt, class UnaryOperation > OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op ); template< class InputIt1, class InputIt2, class OutputIt, class BinaryOperation > OutputIt transform( InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op );
用法:
int add(int a, int b) { return a+b; }
vector<int> vecScoreMath; vecScoreMath.push_back(26); vecScoreMath.push_back(42); vecScoreMath.push_back(72); //transform处理数据 transform(vecScoreMath.begin(), vecScoreMath.end(),//输入数据的范围 vecScoreMath.begin(), //保存结果的容器的开始位置 [](int i){if(i>30 && i<60)i=60;return i;}); //对数据处理的操作函数 copy(vecScoreMath.begin(), vecScoreMath.end(), ostream_iterator<int>(cout, " ")); cout << endl; // 26 60 72 //transform 版本2 vector<int> vecScoreEng = {75, 65, 20}; vector<int> vecScore; vecScore.resize(4); transform(vecScoreMath.begin(), vecScoreMath.end(), //第一个输入数据的范围 vecScoreEng.begin(), //第二个输入数据的开始位置 vecScore.begin(), //保存数据结果的容器的开始位置 add); //对数据处理的操作函数 copy(vecScore.begin(), vecScore.end(), ostream_iterator<int>(cout, " ")); cout << endl; //若第一个容器是 26 60 72 ,第二个容器是 75 65 85 62 ,存结果的容器大小为4,——》结果:101 125 157 0 //若第一个容器是 26 60 72 ,第二个容器是 75 65 ,存结果的容器大小为4,——》结果:101 125 未知数 0 //所以,必须保证 第一个容器的大小 < 第二个容器的大小 。一般两个容器的大小要相等。
- lower_bound() 与 upper_bound() 必须先升序排列。
lower_bound(first,last,value)返回第一个 ≥ value 的迭代器。upper_bound(first,last,value)返回第一个 > value 的迭代器
- equal_range(first, last, value) 返回一个pair 包含 等于value的 范围。必须先升序排列。
template<class ForwardIt, class T> std::pair<ForwardIt,ForwardIt> equal_range(ForwardIt first, ForwardIt last, const T& value) { return std::make_pair(std::lower_bound(first, last, value), std::upper_bound(first, last, value)); }
用法:
vector<int> vecScoreEng = {75, 1, 2, 2, 65, 20}; sort(vecScoreEng.begin(), vecScoreEng.end());//默认升序 // reverse(vecScoreEng.begin(), vecScoreEng.end());//颠倒数组 //使用lower_bound()与upper_bound()函数必须先升序排列 //lower_bound(first,last,value)返回第一个 ≥ value 的迭代器 //upper_bound(first,last,value)返回第一个 > value 的迭代器 auto lower = lower_bound(vecScoreEng.begin(), vecScoreEng.end(), 20); auto upper = upper_bound(vecScoreEng.begin(), vecScoreEng.end(), 20); copy(lower, upper, ostream_iterator<int>(cout, " ")); /* auto it = equal_range(vecScoreEng.begin(), vecScoreEng.end(), 20); copy(it.first, it.second, ostream_iterator<int>(cout, " ")); //这两句与上3句等价,输出20 cout << endl; */ // 升序后:1 2 2 20 65 75 // 第一个 ≥ 20 的是 20的位置,第一个 > 20 的是 65的位置,故输出 20
- max_element() 与 min_element()
max_element(first,last) 函数返回最大值的迭代器。也可以加 参数comp (可以是 bool 类型比较函数,或函数对象) 来自定义。
min_element(first,last) 函数返回最小值的迭代器。也可以加 参数comp (可以是 bool 类型比较函数,或函数对象) 来自定义。
10.5 实战STL算法
Student st1("ChenLianqiao", 173); Student st2("JiaWei", 163); Student st3("JiaJunpeng", 187); vector<Student> vecStu; vecStu.push_back(st1); vecStu.push_back(st2); vecStu.push_back(st3); Student st4("Lee", 164); Student st5("Wang", 180); Student st6("Liu", 163); Student st7("ChenLianqiao", 173); vector<Student> vecStuC2; vecStuC2.push_back(st4); vecStuC2.push_back(st5); vecStuC2.push_back(st6); vecStuC2.push_back(st7); vecStu.resize(vecStu.size() + vecStuC2.size()); copy_backward(vecStuC2.begin(), vecStuC2.end(), vecStu.end());//参考上面的介绍 //当vector中存的是对象,用mem_fun_ref()将成员函数地址构造成函数对象 //当vector中存的是对象的指针,用mem_fun()将成员函数地址构造成函数对象 //ptr_fun() 函数将一个普通的函数指针转换为一个函数对象 sort(vecStu.begin(), vecStu.end(), sortbyHeight); for_each(vecStu.begin(), vecStu.end(), mem_fun_ref(&Student::ReportName)); cout << endl; //删除冗余数据 //先sort()升序,再unique()——自定义类型要重载"==",后erase() // //第一步,排序。上面已做过 // //第二步:unique() // vector<Student>::iterator it = unique(vecStu.begin(), vecStu.end()); // //第三步:erase() // vecStu.erase(it, vecStu.end()); vecStu.erase(unique(vecStu.begin(), vecStu.end()), vecStu.end()); //或者合并第二步与第三步 for_each(vecStu.begin(), vecStu.end(), mem_fun_ref(&Student::ReportName)); cout << endl;
常记溪亭日暮,沉醉不知归路。兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。
昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。