C++个人笔记,用于Ctrl+F查询

C++初级笔记

1、快速入门
int main()
{
    return 0;  //返回0,告诉Windows程序运行正常
}

C++的标准库中的标准的输入输出对象,即键盘和显示器。
引用#include ,标准输出对象,std为名称空间,::为作用域操作符,<<为输出操作符:

std::cout << "hello world" << std::endl;  #endl用于换行,将endl输出到cout中

标准输入对象,>>为输入操作符:

std::cout << "请输入两个数:" << std::endl;
int vi,v2;
std::cin >> v1;  #也可使用std::cin >> v1 >> v2;
std::cin >> v2;
std::cout << v1 << "," << v2 << std::endl;
return 0;

使用名称空间using std::in;声明过的,可不用写名称空间,如:cin >> s;
或使用using namespace std;把std中所有的名称都导入进来。
头文件中不能使用using声明。

引用自己定义的头文件:
使用双引号引用自己的头文件如:#include "Dog.h",C++中原有的用尖括号引用:#include ,当引用末尾不写.h的时候,需要使用using namespace std;来引用名称空间,写上.h的时候就可以不写名称空间引用。


2、C++基本内置类型

64位机:
int长度为:4字节
unsigned int长度为:4字节
double长度为:8字节
int指针长度为:8字节
char指针长度为:8字节
double指针长度为:8字节

32位机:
int长度为:4字节
unsigned int长度为:4字节
double长度为:8字节
int指针长度为:4字节
char指针长度为:4字节
double指针长度为:4字节

int型在内存中的存放形式为低位在前高位在后,验证代码:

#include 
int main()
{
	int i = 16777216;
	unsigned char * p = (unsigned char *)(&i);
	printf("第1字节:%d,第2字节:%d,第3字节:%d,第4字节:%d\n",*p,*(p+1),*(p+2),*(p+3));
}

关于char型数组的长度:

char a[] = "abc",b[]={'a','b','c'};
std::cout << sizeof(a) << "," << sizeof(b) << std::endl;  //打印4,3

特殊类型wchar_t,宽字符型(一般占用2-4字节),例如:

#include 
#include 
#include 

using namespace std;
//locale loc("chs");  //windows下ok
int main()
{
    wchar_t wStr[]=L"test sentence!";
    wprintf(L"%ls\n", L"test sentence!");
    wprintf(L"%ls\n", wStr);
    return 0;
}

7、定义对象
int ival(1024);  //可用此方法赋值,直接初始化,用等号为复制初始化
std::string title;  //定义字符串
std::string titleA = "Hello World";
std::string titleB("Hello World");
std::string titleC(10, '9');  //初始化成10个9

全局变量不初始化,系统会自动将其初始化为0。


8、变量的声明和定义
int i;  //定义
extern int i;  //变量声明,该变量在其他源文件。声明不允许初始化,否则变为"定义",只能声明其他文件中的全局变量

C++变量作用域分为:全局作用域、局部作用域、语句作用于、变量屏蔽、类作用域、命名空间作用域。


10、const限定符
const int a = 512;  //定义a为常量,不可修改,运行速度快
const std::string hi = "hello";  //定义时必须初始化
extern const int a;  //声明时,所有文件都要声明,包括定义处也需要extern

const int &b = a;  //必须是const类型的别名

int b = 42;
int &c = b;  //&前边为类型关键字则为别名,否则为引用,修改其中一个b、c都会发生改变  

12、typedef给现有的数据类型定义同义词

可以隐藏特定类型,强调使用类型的目的,简化一些类型定义。
例如:

typedef struct abc {int x; int y; int z} DEF;
DEF b,c;

14、类类型class

关键字:类成员、数据成员、成员函数、公有的、私有的。
C++中的struct比C的强,实质就是class,struct中的默认访问级别是共有的。

class sale_item
{
    public:
    //类的操作:成员函数,一般为共有的,也可用于数据成员
    private:
    //类的数据:数据成员,一般为私有的,不写private默认为私有的
    std::string isbn;  //不可在此处赋值,需要赋值必须给类设计一个构造函数,在构造函数中赋值
    double revenue;
};  //注意:此处有封号,不同于C#

15、编写自己的头文件

1、尖括号包含标准的头文件,C++会在预定义位置查找头文件;
2、双引号包含的头文件和源代码放在一起;
3、写类时,头文件里写类的声明,在源文件里写类的定义;
例如:
sales_item.h中:

#ifndef _SALES_ITEM  //防止头文件多重包含
#define _SALES_ITEM  //大写头文件的名字,使用"_"代替".",叫预处理器变量

#include 
#include 

class sales_item
{
public:
    sales_item();
    void doSomething(std::string, std::string);
    ...
};

#endif

sales_item.cpp中:

#include "sales_item.h"

sales_item::sales_item()
{
    std::cout << "开始执行构造函数。" << std::endl;
    ...
}

void sales_item::doSomething(std::string str_1, std::string str_2)
{
    ...
}

最后,在使用这个类的main.cpp中只需要引用#include "sales_item.h"这个头文件就可以使用这个类了。


17、标准库string类型

string对象不是指针,string类型初始化:

std::string s1;
std::string s2("Hello");
std::string s3(s2);
std::string s4(10, 'a');  //初始化为10个a
std::string s5 = "Hello";  //不推荐这种用法

和C语言对比:

char name[10] = "张飞";  //C,末尾包含'\0'结束符,使用末尾无'\0'的字符数组初始化string对象时会出错
std::string name2("刘备");  //C++,末尾不包含'\0'

命令行读取:

std::cin >> name;  //以空格结束输入,回车进行下一步代码
getline(cin, name);  //读取一整行,空格也会读入,以回车进行下一步

字符串拼接:

string s5 = s1 + "," + s2;  //加号两边至少有一个是字符串变量,否则错误

存放字符串大小的类型string::size_type

string::size_type size = str.size();  //使用该类型不使用int的好处在于不怕字符串长度值太大而溢出

string类型常用内置函数:

函数名 描述
str.size() 返回字符串的长度
str.empty() 字符串为空返回1
str1.compare(str2) 从首字母开的ASCII值始比较两个字符串大小

字符串成员判断函数:

函数名 描述
ispunct(str[n]) 检查字符是否为标点符号,不含空格
isalnum(str[n]) 检查是否为数字或字母
isalpha(str[n]) 检查是否是字母
iscntrl(str[n]) 检查是否是控制字符
isdigit(str[n]) 检查是否是数字
islower(str[n]) 检查是否是小写字母
isspace(str[n]) 检查是否是空格
isupper(str[n]) 检查是否是大写字母
isxdigit(str[n]) 检查是否是16进制数
tolower(str[n]) 变字符为小写
toupper(str[n]) 变字符为大写

21、标准库vector类型

vector是一个动态数组,可变大小,需要引用#include ,vector是一个类模板。

std::vector<int> ivec(10, 2);  //保存int类型数据的vector,也可以是自己的类型,该容器中有10个2,不写2就为10个0
std::vector<int> v2(v1);  //v1、v2类型一致时才可这样初始化
ivec.push_back(10);  //给容器在增加一个10,这个10为第11个下标[10]
vector<int>::size_type i = ivec.size;  //专用于存放vector长度的类型

当容器为空时,不存在下标,不可以使用下标操作容器。


23、迭代器

所有的容器,每一种数据结构(标准库中),都有迭代器,只有一部分容器可以使用类似于数组的下标,迭代器使用优于下标。

std::vector<int> v(10, 8);
std::vector<int>::iterator iter = v.begin();  //begin()函数返回一个迭代器
*iter = 9;  //将第一个8更换为9,迭代器实际上是一个指针,指向第一个数据元素

例如,利用迭代器做循环:

for(std::vector<int>::iterator i = v.begin(); i != v.end; i++)
    std::cout << *i << std::endl;

注意,使用常迭代器不能进行数据修改,如:

std::vector<int>::const_iterator i = v.begin();

25、标准库bitset类型

是用来处理二进制位的数据类型,需引用#inclued

bitset<32> a;  //a的大小是32位二进制全0
bitset<16> b(0xffff);  //16位二进制全1,也可以10进制赋值,自动转为二进制数

string str("111101101100");
bitset<32> f(str, 5, 4);  //从第5位开始的4个位的副本,如果给的位数比bitset的位数短,则前面的空位自动填充0,该f目前为"1101"
bitset<32> g(str, str.size() - 4);  //去最后4位给f,前面的多余位自动填充0,该f目前为"1100"

bitset类型可使用小标进行操作,也有一些常用内置函数可供操作:

函数名 描述
bs.set(n) 把第n位变为1,不带参数就是把所有位置为1
bs.reset(n) 把第n位变为0,不带参数就是把所有位置为0
bs.flip(n) 按位反转,0变1,1变0
bs.to_ulong() 返回10进制数
bs.any() 至少有1个1,返回1
bs.none() 全为0,返回1
bs.count() 返回1的个数
bs.size() 返回bitset对象的长度

30、指针和const限定符

C++规定要用指针指向一个常对象,就必须是指向常对象的指正,这种指针一般用来传参。
例如:

const double pi = 3.14;
const double *cptr;
cptr = &pi;

一个指向常对象的指针是可以指向一个普通对象,不能通过常对象指针去修改常对象值。
常指针指向常量的指针

int *const p;  //常指针,必须声明时初始化,一旦初始化,就不能指向其他对象,但指向的对象可变
const int *p;  //指向常量的指针,指针指向可变,但被指向的对象不可变

对于指向常对象的常指针,只能指向常量,不可指向其他变量,也不可通过指针修改对象值:

const double *const pid = &pi;

32、创建动态数组

动态数组建立在内存中的堆,即自由存储区(大小可以为0),必须使用指针;
静态数组在堆栈上,可在内存上,缺点长度不能变化;
C语言:使用mallocfree创建和释放;
C++:使用newdelete[]创建和释放;
例如:

int *p = (int *)malloc(n * sizeof(int));  //C语言
free(p);

int *p = new int[n];  //C++,用内置类型建立不会初始化,用类类型建立会使用默认构造函数初始化
delete[] p;  //忘记delete会导致内存泄露,p对象为数组则需要写"[]",非数组对象不需要写

操作动态数组:

int *p = new int[10]();  //带上括号可以全初始化为0
*(p + 1) = 8;

33、新旧代码的兼容

C++转C字符串:

string st("hello world");
const char *str = st.c_str();  //c_str()用于返回C风格的字符串
const << str << endl;

数组C数组转C++容器:

const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(int_arr, int_arr + arr_size);  //用数组的数据初始化容器,其中构造函数中两个参数都是指针,第二个指针指向最后一个的下一位

37、位操作符bitset类型

箭头操作符 = 解引用操作符 + 点操作符
例如:(*p).foo;等价于p->foo();


42、new和delete

new用来动态创建对象(内存自由存储区,静态在内存堆栈)必须使用指针,使用的时候需要解引用,delete撤销动态创建的对象;

int *p = new int;  //没有初始化
int *p = new int();  //初始化为0
string *p = new string();  //调用默认构造函数,也可string(10, '9');
delete p;  //不需要写*号

43、显式转换 - 强制类型转换符

static_cast:隐式转换
dynamic_cast:C++特有的,动态转换,动态类型识别,对来个有继承关系的类的对象转换时使用
reinterpret_cast:强制转换
const_cast:C++特有的,const指针对象给非const指针赋值的时候可用

static_cast,例如:

double pi = 3.14159265int num = static_cast<int>(pi);  //能够在编译时做些简单检查
int num = (int)pi;  //C的类型转换不检查

dynamic_cast,例如:

class Animal{};  //基类
calss cat : public Animal{};  //派生类,继承关系
Animal *p = new cat();
cat *pc = dynamic_cats<cat *>(p);

const_cast,例如:

const char *pc_str = "hello";
char *pc = const_cast<char *>(pc_str);

51、处理异常(try、catch、throw)

throw是抛出异常,一般用在想要检测的子函数中,一般为打开文件的函数。
例子:

void my_fun(string str1, string str2)
{
    throw 1;
}

int main()
{
    try
    {
        my_fun("d:\\temp\\1.txt", "d:\\temp\\2.txt");  //若无异常则正常执行
    }
    catch(int e)  \\若throw抛出的为字符串,则catch需写为:catch(const char *e) {printf("%s\n", e);};若抛出的为类异常,则即为catch(类名称 e);若需要捕获所有异常,则为catch(...)
    {
        printf("发生异常:%d\n", e);  //若有异常则e值为throw后的数字
    }

    return 0;
}

52、标准异常

bad_alloc内存分配失败异常(#include ):

class Dog
{
public:
    Dog()
    {
        parr = new int[1000000];  //4MB,若内存不够,则在new时会自动返回一个bad_alloc异常
    }
private:
    int *parr;
};

int main()
{
    try
    {
        Dog *pDog;
        pDog = new Dog();
    }
    catch(bad_alloc err)  //捕获bad_allocation异常
    {
        std::cout << err.what() << std::endl;
    }

    return 0;
}

invalid_argument非法参数异常(#include ),用法类似同上。

编写自己的标准异常(#include ):

class Student
{
public:
    Student(int age)
    {
        if(age < 0 || age > 150)
        {
            throw out_of_range("年龄不能小于0或大于150");  //这里输入的内容为what()函数捕获到的异常内容
        }
        this -> m_age = age;
    }
private:
    int m_age;
};

int main()
{
    try
    {
        Student stu(20);
        std::cout << "未出现异常" << std::endl;
    }
    catch(out_of_range err)
    {
        std::cout << "出现异常" << err.what() << std::endl;  //捕获out_of_range中的内容
    }
    return 0;
}

53、使用预处理器进行调试

打印当前的文件名:std::sout << __FILE__ << std::endl;
打印当前日期:std::sout << __DATE__ << std::endl;
打印当前时间:std::sout << __TIME__ << std::endl;
打印当前行:std::sout << __LINE__ << std::endl;

输出调试信息,在当前工程选项中,C/C++选项、命令行中,附加选项中写入:/DNDEBUG,则如下调试信息不回被打印:

#ifndef NDEBUG
    std::cout << "test" << std::endl;
#endif

使用assert断言,需引用#include

a = 3;
assert(a == 3);  //若a不等于3,程序执行时会打印错误信息

断言也受到/DNDEBUG的控制。


57、vector和其他容器类型的形参

vector形参写法:void fun(vector v);
vector引用形参写法:void fun(vector& v);传引用比传值块
最常用的是传迭代器:void fun(vector::const_iterator beg);


59、main函数处理命令行选项

int main(int argc, char **argv)中,argc为命令行形参个数,**argv为字符串数组,每个命令行选项都是一个字符串。
一般命令行中输入的第一个指令为本exe程序,之后的为命令行选项。
将字符串类型转换为整形:a = atoi(argv[1]);
将字符串类型转换为浮点型:a = atof(argv[1]);


64、内联函数

inline int sum(int a, int b),使用inline定义的函数为内联函数。
在编译时,会在调用处展开子函数,减少调用的很多复杂工作。对于编译器来说是一种建议,不一定展开(大函数、递归调用函数等不会展开)。
内联函数一般放在头文件中。


指针与引用的区别(教程外)

相同点:都是地址的概念,指针指向一块内存,它的内容是所指内存的地址,引用是某块内存的别名。

不同点
1、指针是一个实体,引用仅是个别名;
2、引用使用时无需解引用*,指针需要解引用;
3、引用只能在定义时被初始化一次,之后不可再改变,指针可变;
4、引用没有const类型,指针有const类型,const的指针不可变;
5、引用不能为空,指针可以为空;
6、指针和引用的自增运算++意义不一样;
7、sizeof引用得到的是所指变量或对象的大小,而sizeof指针得到的是指针本身(所指向的变量或对象的地址大小);


65、类的成员函数

涉及知识点:
1、函数原型必须在类中定义;
2、函数体:在类中定义函数体、在类外定义函数体;
3、this指针代表当前对象;
4、const成员函数;

#include 
#include 
#include 

class Sales_item
{
public:  //一般类成员变量都为private类型,此处为了方便定义为一个public
    std::string isbn;  //定义书号,注意类中声明不能使用using省去std名称空间
    unsigned units_sold;  //定义数量
    double revenue;  //定义收入

public:
    double arg_price() const;  //只是一个函数声明,函数体写在类外面,const关键字表示该函数为一个常函数,常函数不会修改类中的参数
    bool same_isbn(const Sales_item &rhs) const  //一般class中只做函数声明,这里演示类中写函数体
    {
        //return this -> isbn.compare(rhs.isbn) == 0;  //字符串比较大小函数compare()
        return this -> isbn == rhs.isbn;  //this是一个指针,代表当前对象  
    }
};

double Sales_item::arg_price() const  //使用四个点表示范围解析,在类外的函数定义,之前需在类中声明过,写法:  类名::函数名
{
    if(this -> units_sold)
    {
        return (this -> revenue / this -> units_sold);
    }
    else
    {
        return 0;
    }
}

int main(int argc, char** argv)
{
    Sales_item item1 = Sales_item();
    Sales_item item2 = Sales_item();

    item1.isbn = "0-201";
    item1.units_sold = 10;
    item1.revenue = 300;

    item2.isbn = "0-201";
    item2.units_sold = 2;
    item2.revenue = 70;
    
    std::cout << item1.arg_price() << std::endl;
    if(item2.same_isbn(item1))
    {
        std::cout << "两本书是同一种书!" << std::endl;
    }

    system("pause");
    return 0;
}

66、类的构造函数

用来给类的数据成员初始化。

涉及知识点:
1、构造函数是特殊的成员函数;
2、构造函数的定义;
3、构造函数的初始化列表;
4、合成的默认构造函数;

构造函数描述:
构造函数名和类名一样,没有返回值(连void都不许写),该函数一般为public,私有的话就无法使用。
在类中的类类型变量不用构造函数初始化,因为它们已经在自己的构造函数中初始化过了,例如:std::string isbn;
内置的普通类型需要构造函数初始化,例如:int Units_sold; double revenue;
对于没有形参的构造函数,不可以在构造处给其传实参,也叫默认的构造函数
写类时若没有写构造函数,C++会自动写一个默认的构造函数,但这样很危险。
用没写构造函数的类去定义一个全局对象或静态局部对象,那么系统给的默认构造函数会给该对象合理的初始化,一般值都为0。但去构造一个普通局部对象则要小心,因为里面的成员都没有初始化。

class Person
{
public:
    int a,b,c;
public:
    Person();  //声明构造函数
};

Person::Person():a(0)  //构造函数的初始化列表,在此处初始化变量
{
    b = 0;  //也可以在函数内初始化,但推荐使用初始化列表
}

67、重载函数

涉及知识点:
1、重载函数:出现在相同作用域的两个函数,名字相同形参不同;
2、好处:不用费心给每个函数起名字;
3、函数重载和重复声明的区别:(错误)形参表相同返回类型不相同,函数不能仅仅基于不同的返回类型二实现重载;
4、何时不重载函数名;

class MyScreen
{
public:
    void move();
    void move(int);
    void move(int, int);
    void move(int, int, char *p);
};

68、重载与作用域

涉及知识点:
1、变量屏蔽(变量隐藏);
2、函数屏蔽(函数隐藏)而不是使用重载;
3、每一个版本的函数重载都应该在同一个作用域中声明;
4、注意:局部地声明函数是一种不明智的方法,函数的声明应该放在头文件中;

//先写三个重载函数
void print(const string&){};
void print(double){};
void print(int){};

void fun1()
{
    void print(int);  //此处发生函数隐藏
    print("hello");  //报错,只能调用void print(int);其他重载的调用会报错
}

void fun2()
{
    int print;  //此处发生函数隐藏,所有重载的调用会报错,只能调用print变量
    print("hello");  //报错
    print(1.23);  //报错
    print(5);  //报错
}

69、函数匹配(重载确定)

重载确定的三个步骤:
1、候选函数
2、选择可行函数
3、寻找最佳匹配(如果有的话)
含有多个形参的重载确定。

//重载函数如下
void f(){}  //1
void f(int a){}  //2
void f(int a, int b){}  //3
void f(double a,double b = 3.14){}  //4

void main()
{
    f(8);  //调用2
    f(5.6);  //调用4,调用2会强制类型转换
    f(42, 2.56);  //发生二义性,编译报错
    f(2.56, 42);  //发生二义性,编译报错
    f(1.2, 8.9);  //调用4
}

70、重载函数:实参类型转换

转换等级:
1、精确匹配;
2、通过类型提升;
3、通过标准转换;
4、通过类类型转换;
参数匹配和枚举类型
重载和const形参

这两个函数为同一个,重载时会出错:

void f(int *const(p)){}
void f(int *p){}

int形参的调用优先级比short调用优先级高:

void ff(int x){}  
void ff(short y){}

同理:doublefloat优先级高。intenum优先级高,形参为enum常量调用时,优先调用int形参类型的重载,只有形参变量为enum定义的时,才调用enum形参类型的重载。


71、指向函数的指针

涉及知识点:
1、用typedef简化函数指针的定义;
2、指向函数的指针的初始化和赋值;
3、通过指针调用函数;
4、函数指针形参;
5、返回指向函数的指针;
6、指向重载函数的指针(返回值和形参必须精确匹配)才可重载到该函数;

例如:
1、pf是一个指向有两个常字符串形参函数的指针:

bool (*pf)(const string&, const string&);
pf = &fun;  //fun为函数名,可以没有&,因为函数名本身就是一个指针,fun必须和定义的指针类型精确匹配才可赋值

2、使用typedef关键字定义指向函数的指针:

typedef bool(*cmpFcn)(const string&, const string&);
cmpFcn pf;
pf = &fun;
std::cout << (*pf)("hello", "point") << std::endl;  //通过指针调用该函数时,需要解引用

3、函数指针形参:

void fun2(int s1, char s2, bool(*pf)(int s3)){}
bool (*pf)(int);
pf = &fun
fun2("hi", "function", pf);  //pf为一个函数指针,也可以是函数名

4、返回指向函数的指针:

int (*fun3(int x))(int*, int){}  //这句的意思是fun3(int x)这个函数,它的返回值为一个int(*)(int*, int)类型

typedef int(*pd)(int*, int);
pd fun3(int x);  //fun3的另一种写法,和上面等效

std::out << fun3(2)(&a, a) << std::endl;  //调用方法形式,相当于一次调用了两个函数

72、标准IO库

设计知识点,面向对象的标准库:
1、继承:基类 -> 派生类;
2、三个头文件;
3、9个标准库类型;
4、IO对象不可复制或赋值;

类间派生关系:

ostream -> ofstream 文件输出流
ostream -> ostringstream 字符串输出流
ostream -> iostream 输入输出流

iostream -> stringstream 字符串流#include
iostream -> fstream 文件流#include

istream -> iostream 输入输出流
istream -> istringstream 字符串输入流
istream -> ifstream 文件输入流

引用文件输出流命名空间std::ofstream fs;,流对象不可以赋值,传引用、传指针是可以的,也不可以将流对象放入vector中,因为放入vector中的对象必须为可以赋值、赋值的。作为返回值也要为传引用或指针,如ofstream& fun(ofstream& of),给父类形参传子类这种叫多态。例如:

void foo(ostream& os){}
foo(std::cout);
ofstream ofs;
foo(ofs);

73、流的条件状态

需引用#include

流对象 流对象状态判断函数
s.eof() 文件是否结束,可用于检测Ctrl+C
s.fail() 失败
s.bad() 严重错误
s.good() 正常
s.clear() 清空其余标志(流状态是由一串二进制数组成),恢复为正常流
s.clear(flag) 清除某种状态,flag为流对象状态,见下表
s.setstate(flag) 设置流为某种状态,flag为流对象状态,见下表
s.rdstate() 读取流状态,存储在std::istream::iostate对象中,一般用于先记录流状态,事后还原流状态
s.ignore(n, str) 忽略输入的前n个错误或对应指令str之前的错误,这两个条件先遇到哪个触发哪个。例如:std::cin.ignore(200, '\n');
流对象状态 含义
std::istream::badbit 错误
std::istream::gootbit 好的状态
std::istream::failbit 下一个状态将失败
std::istream::eofbit 遇到文件结束符

例子:

#include 
using namespace std;
int main()
{
    int value, sum = 0;
    while(cin >> value, !cin.eof())  //检测Ctrl+C,否则不结束循环
    {
        if(cin.bad())  //判断流是否有严重错误
            throw std::runtime_error("IO stream corrupted");
        if(cin.fail())  //当输入一个非int类型时,做修正提示
        {
            cerr << "Bad data, try again" << endl;
            cin.clear();  //恢复流状态
            cin.ignore(200, '\n');  //排除前200个或换行之前的错误,没有这一句当输入错误时"Bad data, try again"会无限打印
            continue;
        }
        sum += value;
        cout << "Sum is " << sum << endl;
    }
    return 0;
}

74、文件流对象的使用

需引用#include

涉及知识点:
1、将文件流对象绑定到文件上;
2、检查文件是否打开成功;
3、将文件流与新文件重新绑定;
4、清除文件流的状态;

例如:

std::string file("c:\\Users\\user\\Desktop\\test.txt");
std::ofstream outfile1(file);  //以输出方式打开文件,若没有该文件则新建该文件
//std::ofstream outfile1(file, std::ifstream::app);  //以追加的方式打开该文件,否则会在打开该文件时清空该文件内容

//std::ofstream outfile2;
//outfile2.open(file.c_str());  //使用open()函数为文件流对象绑定文件,和在初始化时候绑定文件效果等同,但形参必须是C风格的字符串
//outfile2.close();
//outfile2.clear();

if(outfile1)
{
    std::cout << "流状态正常" << std::endl;
    outfile1 << "hello file!";  //给文件写入数据
}

outfile1.close();  //每次用完需要关闭流
outfile1.clear();  //每次用完需要清理流对象状态,否则下次使用流对象是流对象状态可能是不正常的,如:std::istream::eofbit状态

std::ifstream infile(file);  //以读取方式打开文件,若文件不存在不会新建该文件
std::string s;
while(infile >> s)
{
    std::cout << s << std::endl;  //读出文件内容
}
infile.close();
infile.clear();

76、文件模式

涉及知识点:
1、文件模式选项

关键字 模式
std::ifstream::in 输入模式
std::ifstream::out 输出模式,会覆盖
std::ifstream::app 追加模式,不覆盖
std::ifstream::ate 文件指针指向末尾
std::ifstream::trunc 裁剪模式
std::ifstream::binary 二进制模式

2、文件模式组合
outout|appout|truncinin|outin|out|atein|out|trunc

例如:

std::ifstream ifs("file.txt", std::ifstream::in);  //此模式为ifstream默认模式
std::ofstream ofs("file.txt", std::ofstream::out);  //此模式为ofstream默认模式,这种模式会彻底清空源文件内容

std::ofstream ofs("file.txt", std::ofstream::out | std::ofstream::app);  //追加文件内容,追加时,文件指针自动指向文件末尾

这两种模式效果等价std::ofstream::outstd::ofstream::out | std::ofstream::trunc

关于fstream对象,它的文件模式std::fstream::in | std::fstream::out即是它的默认模式,它的输出模式不会把文件原内容清空,追加时,**文件指针自动指向文件开头,会从文件开的字符开始覆盖。**可用std::fstream::ate把文件指针指向文件末尾。

使用getline(inFile, str);来读取一整行文件内容来放入一个字符串,inFile为文件流,str为字符串。


78、字符串流

需要引用#include

涉及知识点:
1、字符串流:内存中的输入输出
std::istringstream字符串输入流、std::ostringstream字符串输出流、std::stringstream三种类型。
2、字符串流stringstream特有的操作

stringstream strm;
stringstream strm(str);
strm.str();
strm.str(str);

3、stringstream提供的转换和格式化

例如:

std::ostringstream oss;  //定义字符串输出流
oss << "hello " << "world " << 13 << endl;
std::cout << oss.str() << std::endl;

std::string dump;
std::string second;
int num;
std::istringstream iss(oss.str());  //定义字符串输入流
iss >> dump;  //将第一个字符串"hello "扔掉
iss >> second;  //获取第二个字符串
iss >> num;  //在读流的时候,会自动转换流为对应的目标类型
std::cout << second << std::endl;
std::cout << num << std::endl;

C++中级笔记

1、顺序容器STL deque类

需引用stl标准模板库头文件#include ,头文件#include

涉及知识点:
1、deque是一个动态数组;
2、dequevector非常类似;
3、deque可以在数组开头和末尾插入和删除数据(vector只能在末尾删除或插入数据);

例如:

#include 
#include 
#include 

int main()
{
    std::deque<int> a;

    a.push_back(3);  //末尾插入数据
    a.push_back(4);

    a.push_front(2);  //开头插入数据
    a.push_front(1);

    a.pop_front();  //开头删除数据,末尾删除数据使用a.pop_back();

    std::deque<int>::iterator iElementLocater;  //deque的迭代器
    for(iElementLocater = a.begin(); iElementLocater != a.end(); iElementLocater++)
    {
        size_t noffset = std::distance(a.begin(), iElementLocater);  //stl中包含distance算法计算角码
        std::cout << "a[" << noffset << "] = " << *iElementLocater << std::endl;
    }

    system("pause");
    return 0;
}

2、顺序容器STL list类

就是双向链表,数据插入时间固定。头文件#include

涉及知识点:
1、实例化std::list对象;
2、在list开头插入元素;
3、在list末尾插入元素;
4、在list中间插入元素;
5、删除list中的元素;
6、对list中元素进行反转和排序;
7、不可使用下标,可以使用迭代器;

例如:

#include 
#include 

int main()
{
    std::list<int> a;
    std::list<int> b(8, 9);  //初始化成8个9

    a.push_front(3);  //开头处插入3

    std::list<int>::iterator iE;  //list的迭代器
    iE = a.insert(a.begin(), 2);  //在开头处用insert插入一个2,返回一个指向插入元素的迭代器,也可以:a.insert(a.end()-2, 3, 4);在end-3处插入3个4

    a.push_front(1);
    
    a.erase(a.begin(), iE);  //删除从开始到iE处的所有成员,不包含iE
    a.erase(iE);  //删除iE迭代器指向的成员

    a.insert(a.begin(), b.begin(), b.end());

    for(std::list<int>::iterator i = a.begin(); i != a.end(); i++)
    {
        std::cout << *i << ", ";
    }
    system("pause");
    return 0;
}

3、堆栈 - STL stack

主要用在系统软件开发,头文件#include #include
涉及知识点:
1、堆栈:LIFO后进先出;
2、自适应容器(容器适配器,它不是个容器);
3、栈适配器STL stack;
4、堆栈与队列无迭代器,即,容器适配器无迭代器;

stack<int, deque<int>> a;
stack<int, vector<int>> b;
stack<int, list<int>> c;
stack<int> d;  //默认stack用deque构造

内置常用函数:

函数 描述
s.empty() 检查堆栈是否为空,为空返回1
s.size() 查看堆栈长度
s.pop() 依据后进先出原则,把一个数据删除,无返回值
s.top() 查看栈顶数据
s.push(item) 把一个数据压入堆栈

例如:

#include 
#include 
#include 
#include 

int main()
{
    std::stack<int, std::deque<int>> a;
    a.push(1);
    a.push(2);
    a.push(3);
    a.push(4);

    std::cout << "现在堆栈里有:" << a.size() << "个数据。" << std::endl;
    while(a.size() != 0)
    {
        int x = a.top();
        a.pop();
        std::cout << "当前删除的数据为:" << x << std::endl;
    }

    system("pause");
    return 0;
}

4、队列 - STL queue

需引用头文件#include

涉及知识点:
1、队列:FIFO先进先出;
2、自适应容器(容器适配器,它不是个容器);
3、栈适配器STL queue;
4、不能用vector构造;
5、堆栈与队列无迭代器,即,容器适配器无迭代器;

std::queue<int, std::deque<int>> a;  //默认用deque构造
std::queue<int, std::list<int>> b;

内置常用函数:

函数 描述
q.empty() 检查堆栈是否为空,为空返回1
q.size() 查看堆栈长度
q.pop() 依据先进先出原则,把一个数据删除,无返回值
q.front() 查看队首数据
q.back() 查看队尾数据
q.push(item) 把一个数据压入堆栈

5、优先级队列 - STL priority_queue

需引用头文件#include

涉及知识点:
1、自适应容器(容器适配器,它不是个容器),不能使用list构造;
2、最大值优先级队列、最小值优先级队列,默认为最大值队列;
3、优先级队列适配器STL priority_queue;
4、堆栈与队列无迭代器,即,容器适配器无迭代器;

std::priority_queue<int, std::deque<int>> pq1;
std::priority_queue<int, std::vector<int>> pq2;  //默认使用vector构造

内置常用函数:

函数 描述
q.empty() 检查堆栈是否为空,为空返回1
q.size() 查看堆栈长度
s.top() 查看队列顶部数据
q.pop() 依据优先级原则,把一个数据删除,无返回值
q.push(item) 把一个数据放入队列

设置最小值优先级队列,例如:

std::priority_queue<int, std::deque<int>, std::greater<int>> pq3;  //默认谓词为std::less,即最大优先级队列

6、顺序容器的定义
顺序容器 特点
vector 快速的随机访问
list 快速增删
deque 可在两端同时操作
顺序容器适配器 名称
stack
queue 队列
priority_queue 优先级队列

1、用非同类容器初始化一个容器,例如:

std::vector<std::string> svec(2, "8");
std::list<std::string> slist(svec.begin(), svec.end());  //形参列表也可以放入其他指针,例如字符串数组指针

2、定义一个容器的容器,例如:

std::vector<std::vector<std::string> > lines1;  //最后一个尖括号前必须要有空格
std::list<std::vector<std::string> > lines2;

7、迭代器和迭代器范围

设计知识点:
1、每一种容器都有自己的迭代器;
2、所有迭代器的接口都是一样的;
3、在整个标准库中,经常使用形参为一对迭代器的构造函数;
4、常用的迭代器操作;
5、*iter++iter--iteriter1 == iter2iter1 != iter2
6、vector和deque容器迭代器的额外操作(实际上是数组):iter + niter - n>>=<<=
7、迭代器范围:v.begin()指向容器中的第一个数据,v.end()指向容器中最后一个数据的下一个,在判断条件中使用v.begin() != v.end()可迭代容器中的全部数据;
8、使迭代器失效的容器操作,如删除操作;


8、顺序容器的操作

内置类型,vectordequelist类似std::list/deque/vector::size_type

成员 说明
size_type 用于存放长度
iterator 迭代器
const_iterator 常迭代器
reverse_iterator 逆序迭代器
const_reverse_iterator 常逆序迭代器
difference_type 保存容器中两个数据间的距离
value_type 存放数据类型,常用在泛型程序设计
reference 存放引用,常用在泛型程序设计
const_reference 存放常引用,常用在泛型程序设计

在容器中添加元素:

函数 说明
push_back(t) 在容器末尾插入数据t
push_front(t) 在容器开头插入数据t
insert(p, t) 在容器p指针前插入数据t
insert(p, n, t) 在容器p指针前插入n个t
insert(p, b, e) 在容器p指针前插入指针b与e之间的数据

对容器使用关系运算符:

if (iv1 > iv2) //是对容器中第一个元素进行对比,若第一个元素相等,则比较长度

改变容器的大小:

函数 说明
size() 返回容器成员数量
max_size() 返回容器能够容纳的最大数量
empty() 检查是否为空,为空返回1
resize() 设置容器大小,不是基于原长度增加
resize(n, t) 设置容器大小,新增加的空间用t填充

访问元素:

函数 说明
back() 返回队尾数据,例如std::vector::reference a = svec.back();
front() 返回队首数据
[n] 下标操作,只适合vectordeque
at[n] 类似下标操作,但容器下标操作越界会抛出异常

删除元素:

函数 说明
erase§ 删除p所指向的成员
erase(b, e) 删除迭代器b-e之间的成员,但不删除e所指向的成员,即[*b, *e)
clear() 全部删除,删除后容器容量不会改变
pop_back() 删除最后一个成员
pop_back() 删除第一个成员,只适用listdeque

赋值与交换:

操作 说明
c1 = c2 把容器c2里的成员逐一替换掉c1里的成员
c1.swap(c2) c1于c2容器里成员相互交换,类型必须相同
c.assign(b, e) 将b,e指针之间的成员[*b, *e),赋值到c的后面
c.assign(n, t) 将n个t赋值到c的后面,类型兼容即可

15、vector容器的自增长

设计知识点:
1、vector是用数组做出来的;
2、capacityreserve函数;

v.capacity()查看容器容量,容量大于或等于容器长度,每当容量不够时,C++会新建一个大数组去把原来的每个值拷贝到新数组中去。当容器长度越长,容器容量自增长范围也就越大,可用v.reserve()提前设定好容量来避免拷贝时花费时间。

v.reserve(10000);
std::cout << v.capacity() << std::endl;

容器排序,例子:

sort(sv.begin(), sv.end());  //容器排序的函数,vector比deque排序快,list最慢

二分法查找某个元素,例子:

if (binary_search(sv.begin(), sv.end(), 70))
    std::cout << "找到该成员70" << std::endl;
else
    std::cout << "未找到70" << std::endl;

17、构造string对象的方法

string内部实质是数组,所以可以使用下标。
构造方法:

std::string s;
std::string s(s2);  //使用s2构造s
std::string s("value");
std::string s(n, 'c');  //初始化为n个'c'
std::string s(b, e);  //b,e为字符串指针
std::string s(cp, n);  //将指针cp的前n个拷贝进来,若没有n则全部拷贝进来
std::string s(s2, pos2);  //pos2为角码,用s2的pos和之后的内容初始化s
std::string s(s2, pos2, len2);  //用s2的pos2处len2长度的内容初始化s,若pos2或len2超出,则有几个拷贝几个

注意:字符数组(即末尾无\0)则初始化string对象会出错。例如:

char cn[] = {'a', 'b', 'c', 'd'};
std::string s1(cn);  //错误
std::string s2(cn, 2);  //正确,只获得a、b这两个成员
std::cout << s1.c_str() << std::endl;  //打印:abcd烫烫烫烫 L
std::cout << s2.c_str() << std::endl;  //打印:ab

18、修改string对象的方法
函数 说明
s.insert(p, t) p为string迭代器,在p处插入t
s.insert(p, n, t) 在p处插入n个t,p指向位置不变
s.insert(p, b, e) 在p处插入b,e迭代器之间的数据
s.insert(pos, n, c) 在下标pos处插入n个c
s.insert(pos, s2) 在下标pos插入s2字符串
s.insert(pos, s2, pos2, len) 在pos处插入s2下标的pos2至len处值
s.insert(pos, cp, len) 在pos标处插入cp所指前len个字符
s.insert(pos, cp) 在pos处插入整个cp数组
s.assign(b, e) s中所有字符都替换为b,e之间字符
s.assign(s2) s2替换掉s
s.assign(s2, pos2, len) s2中的pos下标后len位替换s
s.assign(cp, len) 将cp字符串指针的前len个替换s
s.assign(cp) 将整个cp数组替换掉s
s.erase§ 删除s中p迭代器所指位置字符
s.erase(b, e) 删除[b, e)之间的字符
s.erase(pos, len) 删除pos下标处的len个字符

19、只适合string类型的操作

涉及知识点:
1、三个substr重载函数(获得字符串的一部分,子串);
2、六个append重载函数(在字符串的最后添加新字符);
3、十个replace重载函数(把字符串的一部分,替换为其他字符);

string可以归类为顺序容器,但有一些只有string类型才可以的操作。例如:

std::string s1 = s2.substr(6, 5);  //从s2的小标6开始拿5个元素给s1,若超出则有多少给多少
s1 = s2.substr(6);  //从s2的小标6开始拿所有的元素给s1
s1 = s2.substr();  //从s2拿所有的元素给s1

s1.append("123");  //在s1后添加"123"

s1.replace(11, 3, "4th");  //从第11下标后的3个字符替换为"4th",不包含11本身

20、string类型的查找
函数 说明
s.find(arg) 从左至右查找字符串arg,返回所在位置的下标,类型为std::string::size_type,若未找到返回std::string::npos
s.rfind(args) 同上,从右至左
s.find_first_of(args) 在s中查找args中任一个相符的字符,只找第一个角码并返回
s.find_last_of(args) 在s中查找args中任一个相符的字符,只找最后一个角码并返回
s.find_first_not_of(args) 不匹配arg中每个成员的成员角码,只找第一个角码并返回
s.find_last_not_of(args) 不匹配arg中每个成员的成员角码,只找最后一个角码并返回

例如,找到s3里的所有number中的成员,逐一打印,pos被用在从第n个开始找:

int pos = 0;
std::string s3("123456789123456789123456789");
std::string number("456");
while ((pos = s3.find_first_of(number, pos)) != std::string::npos)
{
    std::cout << s3[pos] << std::endl;
    pos++;
}

21、string对象的比较
函数 说明
s.compare(s2) 和普通关系运算符效果一样,若返回值大于0,则s大
s.compare(pos1, n1, s2) 从pos1处开始取n1个s2相比
s.compare(pos1, n1, s2, pos2, n2) 从s的pos1开始取n1个,与s2的pos2开始取n2个比较
s.compare(cp) 用指向字符串的char型指针与s比较
s.compare(pos1, n1, cp) 同第二行
s.compare(pos1, n1, cp, n2) 同第三行

普通关系运算符比较字符串大小为逐个比较,遇到不一样的就比较ASCII值的大小。


22、STL map与multimap

需引用#inculde
涉及知识点:
1、map映射、multimap多映射,内部结构为红黑树,可以叫做字典关联数组
2、红黑树在数据结构书中;
3、基本操作:insert的四种方法、countfinderase的三种方法;

注意:不能通过find进行修改,但可删除。

insert例子:

std::map<int, std::string> a;
a.insert(std::map<int, std::string>::value_type (1, "one"));  //1位key键,one为value值
a.insert(std::make_pair(-1, "Minus One"));  //无需写出类别,就可以生成一个pair对象
a.insert(std::pair<int, std::string>(1000, "one thousand"));  //和以上类似
a[10000] = "one million";  //下标为key

countfind例子:

std::multimap<int, std::string> ma;
ma.insert(std::multimap<int, std::string>::value_type (10000, "one"));
ma.insert(std::multimap<int, std::string>::value_type (10000, "two"));
ma.insert(std::multimap<int, std::string>::value_type (10000, "three"));

std::cout << "ma里面有:" << ma.count(10000) << "个10000" << std::endl;  //count查找key对应的个数,只用于multimap,map使用起来没意义

std::multimap<int, std::string>::const_iterator fi;
fi = ma.find(10000);  //find返回值为一个指向第一个10000key成员的迭代器,没有找到就指向end()位置
if (fi != ma.end())
{
    size_t n = ma.count(10000);
    for (size_t i = 0; i < n; i++)
    {
        std::cout << "key : " << fi -> first << ", value[" << i << "] = " << fi -> second.c_str() << std::endl;  //first成员返回改指针key值,second返回value值
        fi++;
    }
}
else
{
    std::cout << "没有找到10000!" << std::endl;
}

erase例子:

if (ma.erase(10000) > 0)  //删除所有key为10000的成员,成功后会返回1
{
    std::cout << "删除10000成功!" << std::endl;
}

std::multimap<int, std::string>::iterator iE = ma.find(10000);
if (iE != ma.end())
{
    ma.erase(iE);
    std::cout << "删除10000成功!" << std::endl;
}

ma.erase(ma.lower_bound(10000), ma.upper_bound(10000));  //删除两个指针间的成员,lower_bound返回指向第一个key为10000成员的指针,叫"下界",upper_bound返回指向最后一个key为10000成员的指针,叫"下界"

23、set与multiset

需引用#include
set中只允许有一个相同成员,multiset可允许有多个相同成员,插入时略慢,会自动进行排序。
涉及知识点:
1、set集、multiset多集;
2、红黑树特点速度快;
3、基本操作:insertcountfinderase
注意:不能通过find进行修改,可删除;

insert例子:

std::set<int> a;  //set会默认从小到大排列,默认为set> b;
std::multiset<int> ma;
a.insert(1);
a.insert(2);
a.insert(3);
a.insert(3);  //set中插入多个相同成员时会插入无效
a.insert(4);
a.insert(5);
ma.insert(a.begin(), a.end());

count例子:

std::cout << "multiset里有" << ma.count(3) << "个3" << std::endl;

find例子:

std::set<int>::const_iterator i;
i = a.find(3);  //返回3的迭代器,若未找到,返回a.end()

erase例子:

a.erase(3);  //若是multiset,删除一个成员,会将相同的多个成员一起删除
a.erase(a.begin(), a.end());

24、STL算法简介

即标准模板库算法。
涉及知识点:
1、100多种算法;
2、函数对象;
3、函数适配器;
4、三个头文件#include #include #include
5、预定义的函数对象;
6、预定义的函数适配器;

算法分类:非修改性算法、修改性算法、移除性算法、变序性算法、排序算法、已序区间算法、数值算法。


25、STL函数对象简介

需引用头文件#include
涉及知识点:
1、预定义的函数对象;
2、自定义的函数对象;
3、容器和函数对象;
4、算法和函数对象;

预定义的函数对象:

函数对象 说明
std::negate() 元素求相反数-_Left
std::plus() 两个元素相加_Left + _Right
std::minus() 两个元素相减_Left - _Right
std::multiplies() 两个元素相乘_Left * _Right
std::divides() 两个元素相除_Left / _Right
std::modulus() 两元素取余数_Left % _Right
std::logical_not() 元素取非!_Left
std::logical_and() 两元素取与_Left && _Right
std::logical_or() 两元素取或_Left 丨丨 _Right
std::equal_to() 两元素相等_Left == _Right
std::not_equal_to() 两元素不相等_Left != _Right
std::less() 构造排序容器时,由小到大排列_Left < _Right
std::greater() 构造排序容器时,由大到小排列_Left > _Right
std::less_equal() 构造排序容器时,由小到大排列_Left <= _Right
std::greater_equal() 构造排序容器时,由大到小排列_Left >= _Right

例子:

void print(int elem)
{
    std::cout << elem << std::endl;
}

class PrintInt
{
public:
    void operator()(int elem) const
    {
        std::cout << elem << std::endl;
    }
};

int main()
{
    std::set<int, std::greater<int>> a;  //由大到小排序
    std::set<int, std::less<int>> b;
    std::for_each(a.begin(), a.end(), print);  //for_each算法把前两个指针间成员用print函数处理
    std::for_each(a.begin(), a.end(), PrintInt());  //for_each后边可以使用函数,也可以使用函数对象,operator重载了()运算符
    system("pause");
    return 0;
}

26、STL算法 - 元素计数

涉及知识点:
1、count成员,对所有容器都适用(setmultisetmapmultimap等);
2、count_if可以用函数或函数对象作为谓词,即第三个参数;
3、关联容器的等效成员函数,比通用的count算法快一点;

例如:

bool isEven(int elem)
{
    return elem % 2 == 0;
}

int main()
{
    std::vector<int> ivec;
    ivec.push_back(-3);
    ivec.push_back(-2);
    ivec.push_back(-1);
    ivec.push_back(0);
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);
    ivec.push_back(4);

    int num = std::count(ivec.begin(), ivec.end(), 4);  //查找ivec容器中有几个4
    num = std::count_if(ivec.begin(), ivec.end(), isEven);  //函数名作为一元谓词防暑函数对象位置,用来计算ivec中有几个偶数,函数返回值为1的作为计数条件
    num = std::count_if(ivec.begin(), ivec.end(), std::bind2nd(std::greater<int>(), 4));  //返回值大于4的成员
    num = std::count_if(ivec.begin(), ivec.end(), std::bind2nd(std::modulus<int>(), 2));  //返回ivec中有几个奇数
    num = std::count_if(ivec.begin(), ivec.end(), std::not1(std::bind2nd(std::modulus<int>(), 2)));  //可取反返回,这样得到的为几个偶数
    system("pause");
    return 0;
}

27、STL算法 - 最大值和最小值

涉及知识点:
1、min_element(b, e);
2、min_element(b, e, op);
3、max_element(b, e);
2、max_element(b, e, op);

例如:

bool absLess(int elem1, int elem2)  //二元谓词
{
    return abs(elem1) < abs(elem2);
}

int main()
{
    std::vector<int> ivec;
    ivec.push_back(-3);
    ivec.push_back(-2);
    ivec.push_back(-1);
    ivec.push_back(0);
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);
    ivec.push_back(4);

    std::cout << "最小值" << *std::min_element(ivec.begin(), ivec.end()) << std::endl;  //返回一个迭代器,需要解引用使用
    std::cout << "绝对最小值" << *std::min_element(ivec.begin(), ivec.end(), absLess) << std::endl;
    std::cout << "绝对最大值" << *std::max_element(ivec.begin(), ivec.end(), absLess) << std::endl;

    system("pause");
    return 0;
}

28、STL算法 - 查找算法(1)

查询结果为一个迭代器。
涉及知识点:
1、find();
2、find_if();
3、如果是已序区间,可以使用已序区间查找算法;
4、关联式容器有等效的成员函数find(),效率高于通用算法;
5、string有等效的成员函数find();

find()例如:

std::list<int> ilist;
std::list<int>::iterator pos1, pos2;
pos1 = find(ilist.begin(), ilist.end(), 4);  //查找ilist中的第一个4
if (pos1 != ilist.end())
{
    pos2 = find(++pos1, ilist.end(), 4);  //查找ilist中第二个4
}

find_if()例如:

pos1 = find_if(ilist.begin(), ilist.end(), std::bind2nd(std::greater<int>(), 3));  //查找第一个大于3的数
pos2 = find_if(ilist.begin(), ilist.end(), std::not1(std::bind2nd(std::modulus<int>(), 3)));  //查找第一个能被3整除的数

已序区间查找,例如:

std::set<int> iset;
std::set<int>::const_iterator pos;
pos = iset.find(78);  //已序区间内置成员函数查找速度非常快

29、STL算法 - 查找算法(2)

查找连续的n个匹配的值。
涉及知识点:
1、search_n(b, e, c, v);,形参分别为:一对迭代器、元素个数、元素值;
2、search_n(b, e, c, v, p);,p为谓词;
3、该算法的第二种形式应该是search_n_if(b, e, c, p)但并不存在这种函数

例如:

std::deque<int> ideq;
std::deque<int>::iterator pos;
pos = std::search_n(ideq.begin(), ideq.end(), 4, 3);  //查找ideq的连续4个3,若有返回第一个3的指针,若无返回ideq.end()
pos = std::search_n(ideq.begin(), ideq.end(), 3, 6, std::greater<int>());  //查找大于6的连续3个数,返回第一个数的指针,谓词必须是一个二元谓词
int num = std::distance(ideq.begin(), pos);  //可利用distance计算该位置的角码

30、STL算法 - 查找算法(3)

找到相匹配的区间。
涉及知识点:
1、search();
2、find_end();
3、以上两个算法是一对,第二个应该被命名为search_end(),但是被命名为find_end();

例如:

std::deque<int> ideq;
std::list<int> ilist;
std::deque<int>::iterator pos;
pos = std::search(ideq.begin(), ideq.end(), ilist.begin(), ilist.end());  //找和该list匹配的deque部分,返回第一个值的迭代器
while (pos != ideq.end())  //继续查找
{
    std::cout << "找到角码:" << std::distance(ideq.begin(), pos) << std::endl;
    pos = std::search(pos + 1, ideq.end(), ilist.begin(), ilist.end());  //find_end()函数送后面开始找,用法一致
}
bool check(int elem, bool even)
{
    if (even)
        return elem % 2 == 0;
    else
        return elem % 2 == 1;
}

int main()
{
    std::deque<int> ideq;
    bool even[3] = {true, false, true};
    pos = std::search(ideq.begin(), ideq.end(), even, even + 3, check);  //查找容器中,偶奇偶连续的三个数,check必须为一个二元谓词
    system("pause");
    return 0;
}

31、STL算法 - 查找算法(4)

找两个迭代器区间内的另一个区间中的某一成员。
涉及知识点:
1、find_first_of(b, e, sb, se);
2、find_first_of(b, e, sb, se, bp);
3、使用逆向迭代器,注意,没有find_last_of()算法;

例如:

std::vector<int> ivec;
std::vector<int>::iterator pos;
std::vector<int>::reverse_iterator rpos;  //逆向迭代器
std::list<int> search;

pos = std::find_first_of(ivec.begin(), ivec.end(), search.begin(), search.end());  //返回一个找到的第一个元素的迭代器
rpos = std::find_first_of(ivec.rbegin(), ivec.rend(), search.begin(), search.end());  //因为STL算法中没有类似string中的find_last_of()函数,所以使用逆向迭代器来逆向查找
std::cout << "找到的位置为:" << std::distance(ivec.begin(), rpos.base()) << std::endl;  //rpos.base()指向逆向找到成员的下一个

32、STL算法 - 查找算法(5)

查找连续的两个相等的或符合谓词规则的成员。
涉及知识点:
1、找到第一个连续且相等的成员的第一个的迭代器adjacent_find(b, e);
2、adjacent_find(b, e, p);

例如:

bool doubled(int elem1, int elem2)
{
    return elem1 * 2 == elem2;
}

int main()
{
    std::vector<int> ivec;
    std::vector<int>::iterator pos;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(2);
    ivec.push_back(3);
    std::adjacent_find(ivec.begin(), ivec.end());  //会返回第一个2的迭代器
    pos = std ::adjacent_find(ivec.begin(), ivec.end(), doubled);  //查找第二个成员是前一个两倍的一对

    system("pause");
    return 0;
}

33、STL算法 - 查找算法(6)

几个已序区间的查找算法,返回值为bool,非已序区间会报错。
涉及知识点:
1、binary_search(b, e, v);若找到返回true;
2、binary_search(b, e, v, p);
3、includes(b, e, sb, se);查找多个,全找到返回true;
4、includes(b, e, sb, se, p);谓词部分一般用于容器成员比较特殊(如对象)时指定排列规则;

例如:

std::binary_search(ilist.begin(), ilist.end(), 5);
std::includes(ilist.begin(), ilist.end(), search.begin(), search.end());

34、STL算法 - 查找算法(7)

已序区间算法,返回值为迭代器。
涉及知识点:
1、lower_bound();找到第一个该成员的地址;
2、upper_bound();找到最后一个该成员地址的下一个;
3、equal_range();以上两个功能;
4、关联式容器有等效的成员函数,性能更佳;

例如:

pos1 = std::lower_bound(ilist.begin(), ilist.end(), 5);  //指向第一个5的迭代器
pos2 = std::upper_bound(ilist.begin(), ilist.end(), 5);  //找到最后一个5的迭代器+1
ilist.insert(std::lower_bound(ilist.begin(), ilist.end(), 5), 5);  //比较方便在找到的位置插入新值,不破坏已序区间
ilist.insert(std::upper_bound(ilist.begin(), ilist.end(), 5), 5);

std::pair<std::list<int>::iterator, std::list<int>::iterator> range;  //做一个pair成员,内含有两个迭代器
range = std::equal_range(ilist.begin(), ilist.end(), 5);  //将返回的两个迭代器放入pair
std::cout << distance(ilist.begin(), range.first) << std::endl;  //打印第一个5所在位置的角码
std::cout << distance(ilist.begin(), range.second) << std::endl;

使用multiset的类似成员函数,例如:

std::multiset<int> imset;  //使用关联式容器的成员函数法
imset.upper_bound(5);
imset.lower_bound(5);
imset.equal_range(5);

35、STL算法 - for_each()

对容器中的数据遍历、存取、处理、修改,速度快但不灵活。
涉及知识点:
1、for_each(b, e, p);一对迭代器,一个函数对象或函数。返回值为函数对象;
2、使用for_each()算法遍历数据;
3、使用for_each()和函数对象修改数据;
4、使用for_each()的返回值;

例如:

void print(int elem)
{
    std::cout << elem << ' ';
}

template<class T>
class AddValue
{
private:
    T thevalue;
public:
    AddValue(const T& v): thevalue(v){}
    void operator()(T& elem) const
    {
        elem += thevalue;
    }
};

class MeanValue  //利用for_each的返回值做平均值,返回类型为该函数对象类型
{
private:
    long num;
    long sum;
public:
    MeanValue():num(0), sum(0){}
    void operator()(int elem)
    {
        num++;
        sum += elem;
    }
    double value()
    {
        return static_cast<double>(sum) / static_cast<double>(num);
    }
    operator double()
    {
        return static_cast<double>(sum) / static_cast<double>(num);
    }
};

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    //例子1
    for_each(ivec.begin(), ivec.end(), print);  //使用函数打印ivec
    std::cout << std::endl;

    //例子2
    for_each(ivec.begin(), ivec.end(), AddValue<int>(10));  //利用函数对象修改ivec中的成员,给ivec每个成员加10

    //例子3
    MeanValue mv = for_each(ivec.begin(), ivec.end(), MeanValue());
    std::cout << "平均值:" << mv.value() << std::endl;  //调用value函数来结算平均值
    double result = for_each(ivec.begin(), ivec.end(), MeanValue());
    std::cout << "平均值:" << result << std::endl;  //调用了double重载运算符计算平均值

    system("pause");
    return 0;
}

36、STL算法 - 区间的比较

涉及知识点:
1、equal(b, e, b2);比较b~e之间数据与b2指向数据是否相等,相等返回1;
2、equal(b, e, b2, p);用二元谓词P比较全为true则认为相等;
3、mismatch(b, e, b2);查找两个容器间第一个不相等的数据;
4、mismatch(b, e, b2, p);当谓词P返回true认为相等,当为false认为找到不相等的了;
5、lexicographical_compare(b, e, b2, e2);比较第一个区间是不是比第二个区间小;
6、lexicographical_compare(b, e, b2, e2, p);添加检查第一个区间小于第二个区间的谓词,第一个小为true,否则为false;

例如:

int main()
{
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(4);
    ilist.push_back(5);
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    //例子1
    if (std::equal(ivec.begin(), ivec.end(), ilist.begin()))  //比较ivec与ilist开始到最后的值
    {
    }

    //例子2
    std::pair<std::vector<int>::iterator, std::list<int>::iterator> values;
    values = std::mismatch(ivec.begin(), ivec.end(), ilist.begin());
    if (values.first == ivec.end())
    {
        std::cout << "没找到不相等的值." << std::endl;
    }
    else
    {
        std::cout << "第一对不相等的值:" << *values.first << " and " << *values.second << std::endl;
    }

    //例子3
    values = std::mismatch(ivec.begin(), ivec.end(), ilist.begin(), std::less_equal<int>());  //自行使用谓词判断的方法,less_equal为一对数满足小于等于返回true,找到第一个谓词返回false的数

    //例子4
    if (std::lexicographical_compare(ivec.begin(), ivec.end(), ilist.begin(), ilist.end()))
    {
        std::cout << "ivec小于ilist" << std::endl;  //比较方法和关系运算符规则一样
    }
    else
    {
        std::cout << "ivec不小于ilist." << std::endl;
    }

    system("pause");
    return 0;
}

37、STL算法 - 复制元素(copying)

修改性算法中的一种,用来把数据从一个容器拷贝到另一个中,或在同一个容器中进行拷贝。
涉及知识点:
1、copy();不能给没有容量的容器拷贝元素,要保证容量足够大;
2、copy_backward();从指针处往前拷贝,且样品容器从后往前拷贝;
3、不存在copy_if()算法,可以使用remove_copy_if()算法,就是使用一个谓词,符合条件才拷贝;
4、复制过程中要逆转元素次序,使用reverse_copy()算法;
5、把容器内所有元素赋值给另一个容器,要使用赋值操作符或容器的assign()成员函数;
6、复制过程中删除某些元素,使用remove_copy()remove_copy_if()成员函数;
7、复制中改变元素,使用transform()replace_copy()算法;

例如:

int main()
{
    std::list<int> ilist_empty;
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(4);
    ilist.push_back(5);
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    //例子1
    std::copy(ilist.begin(), ilist.end(), ivec.begin());  //ilist为样本容器,ivec为目标容器
    std::copy_backward(ilist.begin(), ilist.end(), ivec.end());  //从ivec.end()向前拷贝,且样品也从后往前拷贝

    //例子2
    std::copy(ivec.begin(), ivec.end(), std::back_inserter(ilist_empty));  //向ilist这个空容器中拷贝,利用了back_inserter()插入迭代器

    //例子3
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));  //将ivec中的元素拷贝到输出流迭代器里,在终端会显示ivec中的值

    //例子4
    std::copy(ivec.rbegin(), ivec.rend(), ilist.begin());  //逆向拷贝样本中的成员,等价于下一行
    std::reverse_copy(ivec.begin(), ivec.end(), ilist.begin());  //这种方式操作更快

    system("pause");
    return 0;
}

38、STL算法 - transform()

转换、变换,速度快,非常灵活。
涉及知识点:
1、transfrom()算法有两种形式:
transform(b1, e1, b2, op);b1 -> op ->b2
transform(b1, e1, b2, b3, op);b1, b2 -> op ->b3,三个容器可以为同一个容器;
2、如果目标与源相同,transform()就和for_each()一样;
3、如果想以某值替换符合规则的元素,应该使用replace()算法。最好保证目标容器中有位置,否则就要使用插入迭代器back_inserter(),谓词部分传参不需要传引用也能修改自身值,for_each()需要&符,但谓词需要返回值

例如:

int main()
{
    std::list<int> ilist;
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    //例子1
    std::transform(ivec.begin(), ivec.end(), ivec.begin(), std::negate<int>());  //取负值又赋给自己
    std::transform(ivec.begin(), ivec.end(), std::back_inserter(ilist), std::bind2nd(std::multiplies<int>(), 10));  //ivec乘以10后放入ilist中

    //例子2
    std::transform(ivec.begin(), ivec.end(), ivec.begin(), ivec.begin(), std::multiplies<int>());  //ivec自己和自己相乘,结果又放回自己
    std::transform(ivec.begin(), ivec.end(), ivec.rbegin(), std::back_inserter(ilist), std::plus<int>());  //ivec第一个数和ivec最后一个数相加,依次加完结果放入ilist中
    
    system("pause");
    return 0;
}

40、STL算法 - 交换算法

灵活,速度快。
涉及知识点:
1、swap_ranges(b, e, b2);
2、容器的swap()成员函数、赋值操作符也是交换算法;

例如:

int main()
{
    std::list<int> ilist_empty;
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(4);
    ilist.push_back(5);
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    std::swap_ranges(ivec.begin(), ivec.end(), ivec.begin());  //也可用逆向迭代器,只交换两个容器中较小容器长度的值,其余值不变,swap_ranges返回值为一个iterator指向第一个未被交换的值,类型为较长容器的类型
    ilist.swap(ilist_empty);
    
    system("pause");
    return 0;
}

41、STL算法 - 填充新值

会覆盖掉原值,填充该区间的值全为设定值。
涉及知识点:
1、fill(b, e, v);只能填充固定值;
2、fill_n(b, n, v);n为指定插入个数;
3、generate(b, e, p);可用函数对象产生动态新值如rand();
4、generate_n(b, n, p);

例如:

int main()
{
    std::list<std::string> ilist_empty;
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(2);
    ilist.push_back(3);
    std::list<std::string> slist;
    slist.push_back("one");
    slist.push_back("two");
    slist.push_back("three");

    std::fill(slist.begin(), slist.end(), "hello");  //slist中要有值,否则不能填充
    std::fill_n(std::back_inserter(ilist_empty), 9, "hello");  //没有空间的容器就用back_inserter
    std::fill_n(std::ostream_iterator<float>(std::cout, " "), 10, 7.7);  //在中断中打印10个7.7

    std::generate(ilist.begin(), ilist.end(), std::rand);
    std::generate_n(std::back_inserter(ilist), 5, std::rand);  //插入5个随机数
    
    system("pause");
    return 0;
}

43、STL算法 - 删除算法(1)

涉及知识点:
1、remove(b, e, k);
2、remove_if(b, e, p);有if写谓词,无if写常量;
3、并不是真正的删除,而是把后面的元素向前移动,覆盖被删除元素;
4、返回新的逻辑终点;
5、容器中元素的个数并没有减少,长度不变,容器的末尾还是原来数据;

例如:

int main()
{
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(2);
    ilist.push_back(3);

    //例子1
    std::list<int>::iterator en;
    en = remove(ilist.begin(), ilist.end(), 3);  //删除3,末尾有重复性数据,只使用begin()-en间的数据即可
    std::cout << "一共删除了:" << std::distance(en, ilist.end()) << "个数据" << std::endl;
    
    //例子2
    ilist.erase(std::remove_if(ilist.begin(), ilist.end(), std::bind2nd(std::less<int>(), 4)), ilist.end());  //真正意义上的删除,删除小于4的数据,再用ivec.erase删除末尾重复数据
    
    system("pause");
    return 0;
}

44、STL算法 - 删除算法(2)

将原区间中的元素剪切到目标区间中。
1、remove_copy(b1, e1, b2, k);
2、remove_copy_if(b1, e1, b2, p);有if写谓词,无if写常量;

例如:

void print(int elem)
{
    std::cout << elem << ' ';
}

int main()
{
    std::list<int> ilist;
    ilist.push_back(1);
    ilist.push_back(2);
    ilist.push_back(3);
    std::multiset<int> iset;

    //例子1
    std::remove_copy_if(ilist.begin(), ilist.end(), std::inserter(iset, iset.end()), std::bind2nd(std::less<int>(), 2));  //删除掉小于2的数到目标区间,也就是不复制小于2的数,以目标区间为本
    for_each(iset.begin(), iset.end(), print);

    //例子2
    std::remove_copy(ilist.begin(), ilist.end(), std::ostream_iterator<int>(std::cout, " "), 3);  //删除掉3,再打印到cout里
    std::remove_copy_if(ilist.begin(), ilist.end(), std::ostream_iterator<int>(std::cout, " "), bind2nd(std::greater<int>(), 4));  //删除掉大于4的数到cout里
    
    system("pause");
    return 0;
}

45、STL算法 - 删除算法(3)

删除掉连续的重复的元素,可以通过先排序,再使用unique删除所有重复的元素。
涉及知识点:
1、unique(b, e);返回一个迭代器;
2、unique(b, e, p);
3、unique_copy(b1, e1, b2);
3、unique_copy(b1, e1, b2, p);
4、不存在unique_if()算法;
5、不存在unique_copy_if()算法;

例如:

void print(int elem)
{
    std::cout << elem << ',';
}

bool differenceOne(int elem1, int elem2)
{
    return elem1 + 1 == elem2 || elem1 - 1 == elem2;
}

int main()
{
    std::list<int> ilist;
    std::list<int>::iterator pos;
    int source[] = {1, 4, 4, 6, 1, 2, 2, 3, 1, 6, 6, 6, 5, 7, 5, 4, 4};
    int source_num = sizeof(source) / sizeof(source[0]);  //获得数组的长度
    std::copy(source, source + source_num, std::back_inserter(ilist));  //若不是空容器,就back_inserter(ilist.begin())
    
    //例子1
    pos = std::unique(ilist.begin(), ilist.end());  //删除掉连续重复元素,返回逻辑end()
    for_each(ilist.begin(), pos, print);

    //例子2
    pos = std::unique(ilist.begin(), ilist.end(), std::greater<int>());  //删除掉a>b中的b,得到1,4,4,6,6,6,6,7(先注释掉例子1)
    for_each(ilist.begin(), pos, print);

    //例子3
    std::unique_copy(ilist.begin(), ilist.end(), std::ostream_iterator<int>(std::cout, " "));

    //例子4
    std::unique_copy(ilist.begin(), ilist.end(), std::ostream_iterator<int>(std::cout, " "), differenceOne);  //删除比前面大一或小一的数(先注释掉其他例子)
    
    system("pause");
    return 0;
}

46、STL算法 - 逆转和旋转

涉及知识点:
1、reverse();逆转,打颠倒;
2、reverse_copy();
3、votate();旋转,把想要的对象旋转到第一位;
4、votate_copy();

例如:

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);
    ivec.push_back(4);

    //例子1
    std::reverse(ivec.begin(), ivec.end());
    std::reverse_copy(ivec.begin() + 1, ivec.end() - 1, std::ostream_iterator<int>(std::cout, " "));
    
    //例子2
    std::rotate(ivec.begin(), ivec.begin() + 2, ivec.end());  //3,4,1,2
    std::rotate(ivec.begin(), ivec.end() - 1, ivec.end());  //4,1,2,3
    std::rotate(ivec.begin(), std::find(ivec.begin(), ivec.end(), 4), ivec.end());  //4,1,2,3

    //例子3
    std::set<int> iset;
    iset.insert(1);
    iset.insert(2);
    iset.insert(3);
    iset.insert(4);
    std::set<int>::iterator pos = iset.begin();  //注意set中不能使用iset.begin() + 1
    std::advance(pos, 1);  //pos为双向迭代器,一般容器为随机迭代器,所以用advance
    std::rotate_copy(iset.begin(), pos, iset.end(), std::ostream_iterator<int>(std::cout, " "));  //2,3,4,1
    std::rotate_copy(iset.begin(), iset.find(4), iset.end(), std::ostream_iterator<int>(std::cout, " "));  //4,1,2,3

    system("pause");
    return 0;
}

47、STL算法 - 排列组合

求数列的全排列。
涉及知识点:
1、next_permutation();从小到大排序的原始数据变为从大到小的全排列组合;
2、prev_permutation();从大到小排序的原始数据变为从小到大的全排列组合;

例如:

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);

    //例子1
    while (std::next_permutation(ivec.begin(), ivec.end()))  //返回值为ture时说明还能再排序,最后一次返回false时同时会将数据复原为原来顺序
    {
        std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;
    }

    //例子2
    std::reverse(ivec.begin(), ivec.end());  //旋转一下得3,2,1
    while (std::prev_permutation(ivec.begin(), ivec.end()))
    {
        std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;
    }

    system("pause");
    return 0;
}

48、STL算法 - 重排算法、分区算法

涉及知识点:
1、random_shuffle();随机打乱顺序;
2、partition();分区,把元素中符合谓词规则的部分放在前面,其他的为乱序状态,放在后面;
3、stable_partition();稳定的分区算法,把元素中符合谓词规则的部分放在前面,后端数据不改变原来的顺序;

例如:

int main()
{
    std::vector<int> ivec;
    std::vector<int>::iterator pos;
    ivec.push_back(1);
    ivec.push_back(2);
    ivec.push_back(3);
    ivec.push_back(4);
    ivec.push_back(5);
    ivec.push_back(6);
    ivec.push_back(7);

    //例子1
    std::random_shuffle(ivec.begin(), ivec.end());  //修改容器,打乱顺序
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    //例子2
    pos = std::partition(ivec.begin(), ivec.end(), std::not1(std::bind2nd(std::modulus<int>(), 2)));  //修改容器顺序,查找能被2整除的数放在容器前端,后端数据为乱序状态,pos指向两个区的中间位置
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    //例子3
    pos = std::stable_partition(ivec.begin(), ivec.end(), std::not1(std::bind2nd(std::modulus<int>(), 2)));  //修改容器顺序,查找能被2整除的数放在容器前端,后端数据不改变原来的顺序,稳定的分区算法,pos指向两个区的中间位置
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    system("pause");
    return 0;
}

49、STL算法 - 对所有元素排序

涉及知识点:
1、sort(b, e);
2、sort(b, e, p);
3、stable_sort(b, e);
4、stable_sort(b, e, p);稳定的排序,例如谓词依据长度排序时,保证等长度的字符串是原顺序;
5、注意:不适用于list容器,它有成员函数sort();来排序;

例如:

bool lessLength(const std::string s1, const std::string s2)
{
    return s1.length() < s2.length();
}

int main()
{
    std::deque<int> ideq;
    ideq.push_back(6);
    ideq.push_back(7);
    ideq.push_back(8);

    std::deque<std::string> ideqs;
    ideqs.push_back("123456");
    ideqs.push_back("456");
    ideqs.push_back("678");
    ideqs.push_back("1112");

    std::sort(ideq.begin(), ideq.end(), std::greater<int>());  //由大到小排列,默认为有小到大排列
    std::sort(ideqs.begin(), ideqs.end(), lessLength);  //基于字符串长度有小到大排序
    std::stable_sort(ideqs.begin(), ideqs.end(), lessLength);  //保证等长度的字符串是原顺序

    system("pause");
    return 0;
}

50、STL算法 - 局部排序

以对所有元素排序的规则,对局部进行排序,其他的不动,比全局排序快。
涉及知识点:
1、partial_sort(b, se, e);
2、partial_sort(b, se, e, p);
3、partial_sort_copy(sb, se, db, de);sb、se为源,db、de为目标;
3、partial_sort_copy(sb, se, db, de, p);

例如:

int main()
{
    std::deque<int> ideq;
    ideq.push_back(6);
    ideq.push_back(7);
    ideq.push_back(8);
    ideq.push_back(9);
    ideq.push_back(1);
    ideq.push_back(2);
    ideq.push_back(3);
    std::vector<int> ivec30;
    ivec30.resize(30, 0);

    //例子1
    std::partial_sort(ideq.begin(), ideq.begin() + 5, ideq.end());  //1 2 3 6 7 9 8,只对前五个排序
    std::copy(ideq.begin(), ideq.end(), std::ostream_iterator<int>(std::cout, " "));

    std::cout << std::endl;

    //例子2
    std::vector<int>::iterator pos;
    pos = std::partial_sort_copy(ideq.begin(), ideq.end(), ivec30.begin(), ivec30.end(), std::greater<int>());  //长度30的容器未考被部分全为0,若ivec30较短,则只拷贝到其长度位置,超出部分不拷贝
    std::copy(ivec30.begin(), pos, std::ostream_iterator<int>(std::cout, " "));  //partial_sort_copy的返回值为指向排序的最后一个元素的下一个位置的指针,若不用pos用ivec.end()就会多打印一堆0

    system("pause");
    return 0;
}

51、STL算法 - 根据第n个元素排序

以该迭代器为界限排序,仅排序指定区间内的第n个元素,保证角码[n]获得第n小的元素(从0算起),根据计算机函数库的不同,有时候会将一整个区间都排序。
涉及知识点:
1、nth_element(b, n, e);
2、nth_element(b, n, e, p);
3、对比partition()算法;

例如:

int main()
{
    std::deque<int> ideq;
    ideq.push_back(3);
    ideq.push_back(4);
    ideq.push_back(5);
    ideq.push_back(6);
    ideq.push_back(7);
    ideq.push_back(2);
    ideq.push_back(3);
    ideq.push_back(4);
    ideq.push_back(5);
    ideq.push_back(6);
    ideq.push_back(1);
    ideq.push_back(2);
    ideq.push_back(3);
    ideq.push_back(4);
    ideq.push_back(5);

    //例子1
    std::nth_element(ideq.begin(), ideq.begin() + 3, ideq.end());  //将第3个元素放一个第3小的成员
    std::cout << ideq[3] << std::endl;

    //例子2
    std::nth_element(ideq.begin(), ideq.begin() + 3, ideq.end(), std::greater<int>());  //将第3个元素放一个第3大的成员
    std::cout << ideq[3] << std::endl;
    std::copy(ideq.begin(), ideq.end(), std::ostream_iterator<int>(std::cout, " "));

    system("pause");
    return 0;
}

52、STL算法 - Heap算法

涉及知识点:
1、堆排序算法(heapsort);
2、make_heap();把容器里所有数据变成堆;
3、push_heap();把一个数据加入后,用push_heap()重新生成新堆;
4、pop_heap();取出根数据,放到容器末尾;
5、sort_heap();把堆变为普通排序;
6、大根堆、小根堆,就是将最大或最小值放在最根部;

例如:

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(3);
    ivec.push_back(2);
    ivec.push_back(4);

    //例子1
    std::make_heap(ivec.begin(), ivec.end());  //得到4 3 2 1,最大值在最前
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    //例子2
    std::pop_heap(ivec.begin(), ivec.end());  //得到3 1 2 4,把上一个根放到末尾
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    ivec.pop_back();  //根据需要可删除最大元素
    std::cout << std::endl;

    //例子3
    ivec.push_back(17);
    std::push_heap(ivec.begin(), ivec.end());  //得到17 3 2 1,一个数据加入后,用push_heap()重新生成新堆
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    //例子4
    std::sort_heap(ivec.begin(), ivec.end());  //得到1 2 3 17,重排堆,由小到大
    std::copy(ivec.begin(), ivec.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    system("pause");
    return 0;
}

C++高级笔记

2、类定义

类由结构体发展而来。
涉及知识点:
1、类成员;
2、构造函数;
3、成员函数;
4、访问标号实施抽象和封装(不写标号默认为私有成员),public公有、private私有、protected受保护的成员,可继承类外不可访问;
5、成员函数后面加const就不能修改成员变量;
6、使用类型名来简化类:typedef std::string::size_type index;使用类型别名
7、成员函数和构造函数都可以被重载;
8、显示指定inline内联成员函数。写在类里面的函数实体,为内联函数。写在类外的实体,不论声明处还是实现处,加上inline关键字的函数,也会变为内联函数;
9、类声明:前向声明,和子函数一样,必须在使用该类之前声明该类;
在堆栈上创建类的对象:class_A a;
在堆上创建对象,动态的:class_A *p = new class_A;,delete p;


5、隐含的this指针

指向当前对象。
涉及知识点:
1、何时使用this指针;
2、返回this
3、从const成员函数返回*this
4、基于const的重载;
5、可变数据成员,可以被const函数修改的成员类型mutable类型;

例如:

class Screen
{
public:
    typedef std::string::size_type index;
    Screen(index ht = 0, index wd = 0):contents(ht * wd, 'A'), cursor(0), height(ht),width(wd){}
    char get() const  //获得当前光标位置的值
    {
        return contents[cursor];
    }
    char get(index r, index c) const  //获得指定位置的值
    {
        index row = r * width;
        return contents[row + c];
    }

    void move(index r, index c);  //移动光标位置
    void set(index, index, char);  //修改指定位置的值
    void set(char);  //修改当前位置的值
    Screen& move_new(index r, index c);
    Screen& set_new(index, index, char);  //修改指定位置的值
    Screen& set_new(char);  //修改当前位置的值
    const Screen& display(std::ostream &os) const;
    Screen& display(std::ostream &os);

private:
    std::string contents;
    index cursor;
    index height, width;

    void do_display(std::ostream &os) const;
};

void Screen::move(index r, index c)
{
    index row = r * width;
    cursor = row + c;
}

void Screen::set(index r, index c, char ch)
{
    index row = r * width;
    contents[row + c] = ch;
}

void Screen::set(char c)
{
    contents[cursor] = c;
}

Screen& Screen::move_new(index r, index c)
{
    index row = r * width;
    cursor = row + c;
    return *this;  //解引用返回对象自己
}

Screen& Screen::set_new(index r, index c, char ch)
{
    index row = r * width;
    contents[row + c] = ch;
    return *this;
}

Screen& Screen::set_new(char c)
{
    contents[cursor] = c;
    return *this;
}

const Screen& Screen::display(std::ostream &os) const
{
    do_display(os);
    return *this;
}

Screen& Screen::display(std::ostream &os)
{
    do_display(os);
    return *this;
}

void Screen::do_display(std::ostream &os) const
{
    os << contents.c_str() << std::endl;
}

int main()
{
    Screen myScreen(5, 3);
    std::cout << myScreen.get() << std::endl;
    std::cout << myScreen.get(3, 2) << std::endl;

    myScreen.set(3, 2, 'B');
    std::cout << myScreen.get(3, 2) << std::endl;

    //例子1
    myScreen.move(2, 3);
    myScreen.set('C');
    std::cout << myScreen.get() << std::endl;
    //以上两行简洁的写法
    myScreen.move_new(2, 3).set('C');  //这种写法太漂亮了!
    std::cout << myScreen.get() << std::endl;
    myScreen.move_new(4, 0).set_new('X').move_new(4, 1).set_new('Y').set_new('Z');
    std::cout << myScreen.get() << std::endl;

    //例子2
    myScreen.set_new('C').display(std::cout).set_new('K').display(std::cout);  //两次调用的display不是同一个

    system("pause");
    return 0;
}

6、类作用域

涉及知识点:
类作用域
1、使用类的成员;
2、作用域与成员定义;
3、形参表和函数体处于类作用域中;
4、函数返回类型不一定在类作用域中;
类作用域中的名字查找
1、类成员声明的名字查找;
2、类成员定义中的名字查找;
3、类成员遵循常规的块作用域名字查找;
4、函数作用域之后,在类作用域中查找;
5、类作用域之后,在外围作用域中查找;

例如:

int height;
class Screen
{
public:
    typedef std::string::size_type index;
    index get_cursor() const;
    void dummy_fcn(int height);

private:
    int width;
    int cursor;
    int height;
};

Screen::index Screen::get_cursor() const{return 0;}  //类外使用index作为返回值类型
void Screen::dummy_fcn(int height)
{
    cursor = width * height;  //这个height为形参height
    cursor = width * this -> height;  //这个height为类成员变量height
    cursor = width * ::height;  //这个height为全局变量height,只写::为全局作用域
}

int main()
{
    Screen::index ht;  //利用类作用域操作符去类里面的类型

    system("pause");
    return 0;
}

7、构造函数

涉及知识点:
1、作用:保证每个对象的数据成员具有合适的初始值;
2、构造函数初始化式,即初始化列表
3、默认实参与构造函数;
4、默认构造函数;
5、隐式类类型转换;
6、类成员的显示初始化;

初始化列表比构造函数体里初始化更快,const对象只能在初始化列表内初始化,包括引用类型没有构造函数的类类型亦是如此。初始化列表中的成员顺序可以随便写,但定义成员的顺序有效。
只有一个形参的构造函数,会把类成员函数形参隐式转换为类类型,可用explicit关键字消除这个副作用。
例如:

class Data
{
public:
    int ival;
    char *ptr;
};
Data v = {9, "hello"};  //利用C语言中的初始化结构体的方法初始化类,条件是:1、不能有构造函数;2、所有数据成员全是public

8、友元 - 友元函数、友元类

一个函数或类可以操作另一个类的私有类数据成员。
涉及知识点:
1、友元关系;
2、友元普通函数;
3、友元类;
4、类的友元成员函数;

例如:

class Screen;
class Dog
{
public:
    int foo(Screen& screen);
    int koo(Screen& screen);
};

class Screen
{
public:
    typedef std::string::size_type index;
    Screen(index ht = 0, index wd = 0):contents(ht * wd, ' '), cursor(0), height(ht),width(wd){}
    int area() const {return height * width;}  //普通成员函数可在类外被调用,但私有成员只有友元能调用
    friend int calcArea(Screen&);  //设置友元函数
    friend class WindowMgr;  //设置友元类,该类必须定义在本类之后否则会报错,对本类使用类声明都没用
    friend int Dog::foo(Screen&);  //设置类中的成员函数为友元函数

private:
    std::string contents;
    index cursor;
    index height, width;
};

class WindowMgr  //窗口管理类,对screen进行管理
{
public:
    void relocate(int r, int c, Screen& s)
    {
        s.height += r;
        s.width += c;
    }
};

int Dog::foo(Screen& screen) {return screen.height * screen.width;}
int Dog::koo(Screen& screen)
{
    //return screen.height * screen.width;  //因为koo函数不是友元,所以不能使用screen中的私有成员
    return screen.area();
}

int calcArea(Screen& screen) {return screen.height * screen.width;}  //这个函数不是类的成员函数

int main()
{
    Screen a(60, 100);
    std::cout << a.area() << std::endl;
    std::cout << calcArea((a)) << std::endl;

    WindowMgr wm;
    wm.relocate(20, 100, a);
    std::cout << calcArea((a)) << std::endl;

    Dog dog;
    std::cout << dog.foo(a) << std::endl;
    std::cout << dog.koo(a) << std::endl;
    
    system("pause");
    return 0;
}

9、static类成员

可实现两个对象公用一个成员。
涉及知识点:
1、使用static成员的优点:静态成员属于类所有,可通过类操作它,也可通过对象操作它;
2、定义static成员,可以实现封装,可以是私有的,但这时就不能在函数中通过类名操作,可以在函数外部操作;
3、使用类的static成员,不能使用this指针,因为它不属于任何对象;
4、static成员函数,可通过类名::函数名()直接调用该函数,数据成员也一样;
5、static数据成员;

例如:

class Account
{
public:
    Account(std::string name, double money):owner(name), amount(money){}
    static double rate(double new_rate)
    {
        interest_rate = new_rate;  //此处不能使用this指针
        return interest_rate;
    }
private:
    std::string owner;
    double amount;
    static double interest_rate;
    static const int period = 30;  //特例,可以这样初始化,但用法唯一
};

double Account::interest_rate = 0.015;  //静态成员可以这样初始化,但因为成员函数是私有的,所以不能再main中初始化

int main()
{
    Account::rate(0.026);  //可以对公有成员函数直接在函数中调用,也可以用对象调用
    Account a("张三", 1000);
    a.rate(0.018);
    
    system("pause");
    return 0;
}

10、复制构造函数和赋值操作符

一般两个一起写,不写C++会自动帮我们写。
涉及知识点:
复制构造函数的使用情况:
1、对象的定义形式 - 复制初始化;
2、形参与返回值;
3、初始化容器元素;
4、构造函数与数组元素;
赋值操作符:
1、重载赋值操作符;
2、复制和赋值常一起用;
合成的赋值构造函数和赋值操作符;
定义自己的复制构造函数和赋值操作符;
注意:当一个类里的数据成员有指针时,必须自己写复制构造函数和赋值操作符。

例如:

class Sales_item
{
public:
    Sales_item():units_sold(0), revenue(0.0)  //编号1
    {std::cout << "默认构造函数被调用了!" << std::endl;}

    Sales_item(const std::string &book):isbn(book), units_sold(0), revenue(0.0)  //编号2
    {std::cout << "构造函数被调用了!" << std::endl;}

    Sales_item(const Sales_item &orig):isbn(orig.isbn), units_sold(orig.units_sold), revenue(orig.revenue)  //编号3,复制构造函数传引用,一般需要写const
    {std::cout << "复制构造函数被调用了!" << std::endl;}

    Sales_item& operator=(const Sales_item &rhs)  //编号4,赋值操作符重载
    {
        isbn = rhs.isbn;
        units_sold = rhs.units_sold;
        revenue = rhs.revenue;
        std::cout << "赋值操作符被调用了!" << std::endl;
        return *this;
    }

    Sales_item foo(Sales_item item)
    {
        Sales_item temp;
        temp = item;
        return temp;
    }
private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

int main()
{
    Sales_item a;  //1
    Sales_item b("0-1");  //2
    Sales_item c(b);  //3
    a = b;  //4
    Sales_item item = std::string("9-9");  //2

    Sales_item ret;
    ret = ret.foo(item);  //1、3、1、4、3、4

    std::vector<Sales_item> svec(5);  //1、3、3、3、3、3
    Sales_item primer_eds[] = {std::string("0-1"), std::string("0-2"), Sales_item()};  //2、2、1
    
    system("pause");
    return 0;
}

若类中有指针成员,例如:

class Noname
{
public:
    Noname():pstring(new std::string), i(0), d(0){}
    Noname(const Noname& other):pstring(new std::string(*(other.pstring))), i(0), d(0){}  //默认的复制重载函数此处会写成:pstring(other.pstring),导致浅复制
    Noname& operator=(const Noname &rhs)
    {
        pstring = new std::string;  //默认的赋值操作符会写成pstring = rhs.pstring;
        *pstring = *(rhs.pstring);
        i = rhs.i;
        d = rhs.d;
    }
private:
    std::string *pstring;
    int i;
    double d;
};

11、析构函数

涉及知识点:
1、构造函数:获取资源;
2、析构函数:释放资源,没有形参,只能写一个,写了以后C++还会执行默认的析构函数;
3、合成的析构函数;
4、三法则:写了析构函数就要写复制构造函数和赋值操作符;

例如:

class Noname
{
public:
    Noname():pstring(new std::string), i(0), d(0){std::cout << "构造函数被调用了!" << std::endl;}  //1
    ~Noname();
private:
    std::string *pstring;
    int i;
    double d;
};

Noname::~Noname()
{
    std::cout << "析构函数被调用了!" << std::endl;  //2
    delete pstring;
}

int main()
{
    Noname a;  //1
    Noname *p = new Noname;  //1
    delete p;  //2

    system("pause");
    return 0;  //2
}

12、深复制、浅复制

默认复制构造函数为浅复制。
涉及知识点:
1、复制构造函数,又叫拷贝构造函数;
2、浅复制,又叫浅拷贝、位拷贝。即两个对象的内部某指针指向同一个位置;
3、深复制,又叫深拷贝。即两个对象的内部某指针指向两个位置;
4、浅复制时,修改指针成员,那么其他所有对象的指针对象就都被改值了,普通变量不影响;
5、记得要写析构函数delete
6、若没有深复制,析构函数只要析构了一个类对象,那么其他对象的指针成员就被析构了,成了悬垂指针,也叫野指针


13、管理指针成员

涉及知识点:
1、常规指针类(浅复制),严重缺点:悬垂指针;
2、智能指针类(计数类),即两个对象的内部某指针指向一个智能指针,该智能指针指向一个位置;
3、值类型(深复制);

例如:

class UPtr  //智能指针类,特点和浅复制一样,仅仅只避免野指针
{
public:
    friend class BHasPtr;
private:
    int *ip;
    std::size_t use;  //计数
    UPtr(int *p):ip(p), use(1){}
    ~UPtr(){delete ip;}
};

class BHasPtr
{
public:
    BHasPtr(int *p, int i):ptr(new UPtr(p)), val(i){}  //若是浅复制会写为:ptr(p)
    BHasPtr(const BHasPtr &orig):ptr(orig.ptr), val(orig.val){++ptr->use;}  //复制构造函数
    BHasPtr& operator=(const BHasPtr&);  //重点
    ~BHasPtr()
    {
        if(ptr->use == 0)  //若该类没有对象成员,才触发delete
            delete ptr;
    }

    int *getPtr() const {return ptr->ip;}
    int getInt() const {return val;}

    void setPtr(int *p) {ptr->ip = p;}
    void setInt(int i) {val = i;}

    int getPtrVal() const {return *ptr->ip;}
    void setPtrVal(int val) const {*ptr->ip = val;}
private:
    int val;
    UPtr *ptr;
};

BHasPtr& BHasPtr::operator=(const BHasPtr &rhs)  //赋值操作符重载有技巧,先给等号右边加1,再给左边减1,若计数为0,说明自己赋值自己,计数不变
{
    ++rhs.ptr->use;
    if (--ptr->use == 0) delete ptr;
    ptr = rhs.ptr;
    val = rhs.val;
    return *this;
}

void testBHasPtr()
{
    int obj = 0;
    BHasPtr ptr1(&obj, 42);
    BHasPtr ptr2(ptr1);
    std::cout << ptr1.getPtrVal() << "," << ptr1.getInt() << std::endl;
    std::cout << ptr2.getPtrVal() << "," << ptr2.getInt() << std::endl;

    std::cout << "修改以后:" << std::endl;
    ptr2.setPtrVal(2);
    ptr2.setInt(22);
    std::cout << ptr1.getPtrVal() << "," << ptr1.getInt() << std::endl;
    std::cout << ptr2.getPtrVal() << "," << ptr2.getInt() << std::endl;
}

int main()
{
    testBHasPtr();
    system("pause");
    return 0;
}

14、重载操作符的定义

涉及知识点:
1、可以重载的操作符有42个,例如:+-*/%^&|~等;
2、不能重载的操作符有4个,有:::.*.?:
3、重载操作符的注意事项,一般形参都是操作符两边的参数,左对左,右对右,若只有一个形参,则就是右边的参数,而左边的参数就是this
注意:
1、void operator+(int, int);错误,两个参数至少得有一个是类类型,不允许重载C++内置类型;
2、重载操作符不会修改操作符的优先级,也不会改变结核性;
3、重载操作符不具备短路操作&&||,一般也不会重载这两个操作符,包括&,
4、一般把算术操作+-*/关系操作><==作为非成员函数,使用友元,赋值操作=作为成员函数;
5、也可以这样用operator+(item1, item2),用调用函数的方式;
6、习惯性的做法,+重载时去掉用+=重载;
7、一般重载==就要重载!=,重载了<就要重载<=>>===

例如:

Sales_item operator+(const Sales_item& rhs){}  //第一个参数默认为this,所以只有一个形参代表加号后的参数

或者:

friend Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs);  //若不是类成员函数,为友元函数,则需要写两个形参

或者:

friend std::istream& operator>>(std::istream&, Sales_item&);

15、操作符重载 - 输入输出操作符

涉及知识点:
1、输出操作符<<重载;
2、非成员函数友元函数
3、少做格式化;
4、输出操作符>>重载;
5、处理输入操作的错误;

例如:

class Sales_item
{
public:
    Sales_item():units_sold(0), revenue(0.0){}
    friend std::ostream& operator<<(std::ostream& out, const Sales_item& s);
    friend std::istream& operator>>(std::istream& in, Sales_item& s);
    
private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

std::ostream& operator<<(std::ostream& out, const Sales_item& s)
{
    out << s.isbn << "\t" << s.units_sold << "\t" << s.revenue;
    return out;
}

std::istream& operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.isbn >> s.units_sold >> price;
    if (in)  //处理输入操作的错误,如果in对象有错误,得到一个空对象
        s.revenue = s.units_sold * price;
    else
        s = Sales_item();
    return in;
}

int main()
{
    Sales_item item;
    std::cout << item << std::endl;
    std::cin >> item;
    std::cout << item << std::endl;
    
    system("pause");
    return 0;
}

16、操作符重载 - 算术操作符

涉及知识点:
1、算术操作符:++=--=**=//=%%=
2、为了与内置操作符保持一致,算术操作符通常产生一个新值;
3、一般应使用复合赋值实现算术操作符,例如用+=实现+

例如:

class Sales_item
{
public:
    Sales_item(const std::string& book, const unsigned units, const double amount):isbn(book),units_sold(units),revenue(amount){}
    Sales_item& operator+=(const Sales_item&);  //+=返回值需要为引用,且因为要调用类成员,所以要成为类的成员函数

private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

Sales_item operator+(const Sales_item&, const Sales_item&);  //函数声明,不需要成为成员函数

Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
    this->units_sold += rhs.units_sold;
    this->revenue += rhs.revenue;
    return *this;
}

Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
    Sales_item ret(lhs);
    ret += rhs;  //用+调用+=比较方便,且不需要成为类成员函数
    return ret;
}

17、操作符重载 - 关系操作符

涉及知识点:
1、关系操作符:==!=<<=>>=
2、最好相互关联起来进行重载,让一个操作符调用另一个操作符;

例子1:

class Sales_item
{
public:
    Sales_item(const std::string& book, const unsigned units, const double amount):isbn(book),units_sold(units),revenue(amount){}
    friend bool operator==(const Sales_item &lhs, const Sales_item &rhs);

private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.isbn.compare(rhs.isbn) == 0;
}

inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs);  //技巧,此处无需做成友元,因为==重载是友元
}

例子2:

class Date
{
public:
    int operator==(Date &dt) const;
    int operator<(Date &dt) const;

private:
    int year;
    int month;
    int day;
};

int Date::operator==(Date &dt) const  //一个形参时,dt为==右边的参数,左边的参数为this
{
    return this->month == dt.month && this->day == dt.day && this->year == dt.year;
}

int Date::operator<(Date &dt) const  //因为会调用类成员,所以要写成类成员函数或友元函数
{
    if (this->year == dt.year)
    {
        if (this->month == dt.month)
        {
            return this->day < dt.day;
        }
        return this->month < dt.month;
    }
    return this->year < dt.year;
}

19、重载操作符 - 赋值操作符

涉及知识点:
1、赋值操作符:=+=-=*=/=%=&=|=^=<<=>>=
2、赋值操作必须返回对*this的引用;

例如:

class MyString
{
public:
    MyString(char const *chars = " ");
    MyString &operator=(MyString const &);  //用于处理mystr1 = mystr2;的情况
    //MyString &operator=(char const*);  //用于处理mystr = "hello";的情况
    //MyString &operator=(char);  //用于处理mystr = 'A';的情况

private:
    char *ptrChars;
};

MyString::MyString(char const *chars)
{
    chars = chars ? chars : "";
    ptrChars = new char[std::strlen(chars) + 1];
    std::strcpy(ptrChars, chars);
}

MyString &MyString::operator=(MyString const &str)
{
    if (std::strlen(ptrChars) != std::strlen(str.ptrChars))
    {
        char *ptrHold = new char[std::strlen(str.ptrChars) + 1];
        delete[] ptrChars;
        ptrChars = ptrHold;
    }
    std::strcpy(ptrChars, str.ptrChars);
    return *this;
}

20、操作符重载 - 下标操作符

涉及知识点:
1、下标操作符[]
2、两个版本:可变成员函数、常量成员函数const

例如:

class MyString
{
public:
    MyString(char const *chars = " ");
    void display() const;
    char &operator[](std::size_t index) throw(MyString);  //可变成员函数
    char operator[](std::size_t index) const throw(MyString);  //常量成员函数

private:
    char *ptrChars;
    static MyString ErrorMessage;
};

MyString::MyString(char const *chars)
{
    chars = chars ? chars : "";
    ptrChars = new char[std::strlen(chars) + 1];
    std::strcpy(ptrChars, chars);
}

void MyString::display() const
{
    std::cout << ptrChars << std::endl;
}

MyString MyString::ErrorMessage("Subscript out of range");  //私有成员,不能在main中初始化

char &MyString::operator[](std::size_t index) throw(MyString)
{
    if (index >= std::strlen(ptrChars))  //若输入的下标大于最后一个位置,则抛出异常
        throw ErrorMessage;
    return ptrChars[index];
}

char MyString::operator[](std::size_t index) const throw(MyString)  //返回值不能是引用
{
    if (index >= std::strlen(ptrChars))
        throw ErrorMessage;
    return ptrChars[index];
}

int main()
{
    MyString s("hello");
    std::cout << s[0] << std::endl;
    s[0] = 'a';
    std::cout << s[0] << std::endl;

    system("pause");
    return 0;
}

21、操作符重载 - 成员访问操作符

当一个类中有指针成员时需要使用智能指针类,智能指针类重载成员访问操作符,就可以使用智能指针类去调用被服务对象的成员和解引用。
涉及知识点:
1、解引用操作符*
2、符头操作符->

例如:

class MyString
{
public:
    MyString(char const *chars = "");
    MyString(MyString const &str);
    ~MyString();
    void display() const;
    friend class Pointer;
private:
    std::size_t use;  //计数
    char *ptrChars;
};

MyString::MyString(char const *chars):use(1)
{
    chars = chars ? chars : "";  //如果chars是空指针,就让它指向空字符串
    ptrChars = new char[std::strlen(chars) + 1];
    std::strcpy(ptrChars, chars);
}

MyString::MyString(MyString const &str):use(1)
{
    ptrChars = new char[std::strlen(str.ptrChars) + 1];
    std::strcpy(ptrChars, str.ptrChars);
}

MyString::~MyString()
{
    delete[] ptrChars;
}

void MyString::display() const
{
    std::cout << ptrChars << std::endl;
}

class Pointer
{
public:
    Pointer();
    Pointer(MyString const &n);
    Pointer(const Pointer &orig):ptr(orig.ptr){++ptr->use;}  //复制构造函数
    ~Pointer();
    Pointer& operator=(const Pointer&);
    MyString &operator*();
    MyString *operator->() const;
private:
    MyString *ptr;
    static MyString errorMessage;
};

Pointer::Pointer():ptr(new MyString("")){}

Pointer::Pointer(MyString const &n)
{
    ptr = new MyString(n);
}

Pointer::~Pointer()
{
    if (ptr->use == 0)
    {
        delete ptr;
    }
}

MyString Pointer::errorMessage("Uninitialized pointer");

Pointer& Pointer::operator=(const Pointer &rhs)  //赋值操作符重载有技巧,先给等号右边加1,再给左边减1,若计数为0,说明自己赋值自己,计数不变
{
    ++rhs.ptr->use;
    if (--ptr->use == 0) delete ptr;
    ptr = rhs.ptr;
    return *this;
}

MyString &Pointer::operator*()
{
    if (!ptr)  //判断是否为空指针
        throw errorMessage;
    return *ptr;
}

MyString *Pointer::operator->() const
{
    if (!ptr)
        throw errorMessage;
    return ptr;
}

int main()
{
    try
    {
        Pointer p1("C++");  //会去调用MyString类的第一个构造函数
        p1->display();

        MyString s = *p1;
        s.display();

        Pointer p2;
        p2->display();
    }
    catch (MyString const &error)
    {
        error.display();
    }

    system("pause");
    return 0;
}

22、操作符重载 - 自增减操作符

涉及知识点:
1、自增、自减操作符++--
2、重载前缀形式、后缀形式的自增自减操作符。operator++();前加加,返回引用,operator++(int);后加加,返回拷贝;

例如:

class MyString
{
public:
    MyString(char const *chars = " ");
    MyString(MyString const &str);
    ~MyString();
    void display() const;
    MyString &operator++();
    MyString const operator++(int);

private:
    char *ptrChars;
};

MyString::MyString(char const *chars)
{
    chars = chars ? chars : "";
    ptrChars = new char[std::strlen(chars) + 1];
    std::strcpy(ptrChars, chars);
}

MyString::MyString(MyString const &str)
{
    ptrChars = new char[std::strlen(str.ptrChars) + 1];
    std::strcpy(ptrChars, str.ptrChars);
}

MyString::~MyString()
{
    delete[] ptrChars;
}

void MyString::display() const
{
    std::cout << ptrChars << std::endl;
}

MyString &MyString::operator++()  //给每个字符的ASCII加1
{
    for (std::size_t i = 0; i < std::strlen(ptrChars); i++)
        ++ptrChars[i];
    return *this;
}

MyString const MyString::operator++(int)  //C++默认给形参传0,但此处形参无用
{
    MyString copy(*this);
    ++(*this);
    return copy;
}

int main()
{
    MyString str1("ABC");
    str1.display();  //ABC
    MyString str2(++str1);
    str2.display();  //BCD
    MyString str3(str1++);
    str3.display();  //BCD
    str1.display();  //CDE

    system("pause");
    return 0;
}

23、函数对象

涉及知识点:
1、重载函数调用操作符;
2、函数对象:定义了调用操作符的类,其对象称为函数对象
3、一元函数对象与一元谓词;
4、二元函数对象与二元谓词;

使用了函数重载操作符的类,定义的对象都叫函数对象,一般用struct来写。由于STL包括容器和算法都是使用模板设计的,在自己设计函数对象时也应该设计模板,即带模板的函数对象。函数对象比普通函数功能更强大一些,它是可以有数据成员的,因此就可以保持状态,这是普通函数所不具备的功能,例如保存调用次数;
如果函数对象返回值为bool型,那么这种函数对象称作谓词,如果函数对象只有一个形参,返回值为bool,那么这个函数对象称作一元谓词,返回值不为bool,则称作一元函数对象,二元函数对象同理。

函数对象,例子:

template<typename elementType>
void funcDisplayElement(const elementType &element)  //普通函数,这种用法不好
{
    std::cout << element << " ";
}

template<typename elementType>
struct DisplayElement
{
    int m_nCount;  //比普通函数强大,增加成员变量
    DisplayElement()  //构造器,类似构造函数
    {
        m_nCount = 0;
    }
    void operator() (const elementType &element)
    {
        ++m_nCount;  //可以记住被调用了多少次
        std::cout << element << ' ';
    }
};

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(3);
    ivec.push_back(2);
    ivec.push_back(4);
    for_each(ivec.begin(), ivec.end(), DisplayElement<int>());  //匿名对象用法
    DisplayElement<int> mResult;  //命名函数对象,就可以调用对象内的成员了,匿名不行
    mResult = for_each(ivec.begin(), ivec.end(), mResult);
    std::cout << "数量:" << mResult.m_nCount << std::endl;
    
    system("pause");
    return 0;
}

一元谓词,例子:

template<typename numberType>
struct IsMultiple
{
    numberType m_Divisor;
    IsMultiple(const numberType& divisor)
    {
        m_Divisor = divisor;
    }
    bool operator()(const numberType &element) const
    {
        return ((element % m_Divisor) == 0);  //找出能被m_Divisor整除的数
    }
};

int main()
{
    std::vector<int> ivec;
    ivec.push_back(1);
    ivec.push_back(3);
    ivec.push_back(4);
    ivec.push_back(2);
    std::vector<int>::iterator iElement = find_if(ivec.begin(), ivec.end(), IsMultiple<int>(2));  //匿名写法
    IsMultiple<int> a(2);
    iElement = find_if(ivec.begin(), ivec.end(), a);

    if (iElement != ivec.end())
    {
        std::cout << *iElement << std::endl;
    }
    
    system("pause");
    return 0;
}

二元函数对象,例子:

template<typename elementType>
class CMultiple  //使用class做函数对象就需要将调用操作符()定义为public
{
public:
    elementType operator()(const elementType &elem1, const elementType &elem2)
    {
        return (elem1 * elem2);
    }
};

int main()
{
    std::vector<int> ivec1, ivec2, vecResult;
    vecResult.resize(4);
    ivec1.push_back(1);
    ivec1.push_back(3);
    ivec1.push_back(4);
    ivec1.push_back(2);
    ivec2.push_back(5);
    ivec2.push_back(8);
    ivec2.push_back(7);
    ivec2.push_back(6);
    std::transform(ivec1.begin(), ivec1.end(), ivec2.begin(), vecResult.begin(), CMultiple<int>());
    for (size_t i = 0; i < vecResult.size(); i++)
        std::cout << vecResult[i] << " ";
    
    system("pause");
    return 0;
}

二元谓词,例子:

class CCompareStringNoCase
{
public:
    bool operator()(const std::string &str1, const std::string &str2) const
    {
        std::string str3, str4;
        str3.resize(str1.size());
        std::transform(str1.begin(), str1.end(), str3.begin(), tolower);  //tolower为内置函数指针,将字符串变小写
        str4.resize(str2.size());
        std::transform(str2.begin(), str2.end(), str4.begin(), tolower);
        return str3 < str4;
    }
};

int main()
{
    std::set<std::string, CCompareStringNoCase> names;  //可以不区分大小写的查找成员
    names.insert("Bob");
    names.insert("Sam");
    std::set<std::string, CCompareStringNoCase>::iterator iNameFound = names.find("SAM");
    if (iNameFound != names.end())
        std::cout << *iNameFound << std::endl;
    
    system("pause");
    return 0;
}

24、转换操作符

涉及知识点:
1、转换函数:operator int() const;还可以是doublefloatstd::sting等;
2、必须是成员函数;
3、不能指定返回类型;
4、形参表必须是空的;
5、必须显示地返回一个指定类型的值;
6、不应该改变被转换对象,通常定义为const

例如:

class Dog
{
public:
    operator int() const {return age;}
private:
    int age;
};

int main()
{
    int a;
    Dog b;
    a = b;
    
    system("pause");
    return 0;
}

25、定义基类和派生类

涉及知识点:
1、基类:virtual函数、protected函数,派生类继承基类共有的和受保护的成员;
2、派生类:类派生列表、构造函数不能写成虚函数、重定义virtual函数、类中有虚函数时必须写析构函数;
3、派生类可以访问基类的publicprotected成员,受保护的成员是专用来做继承的作用域和private一样;

例如:

class ItemBase  //基类,原价卖书
{
public:
    ItemBase(const std::string &book = " ", double sales_price = 0.0):isbn(book), price(sales_price){}
    std::string book(){return isbn;}
    virtual double net_price(size_t n) const{return n * price;}  //虚函数在子类中可以重做,而普通函数不行
protected:
    double price;
private:
    std::string isbn;
};

class BulkItem : public ItemBase  //派生类,打折卖书
{
public:
    //qty为最低打折数量,默认为9,折扣值为0;派生类调用基类构造函数初始化基类
    BulkItem(const std::string &book = " ", double sales_price = 0.0, size_t qty = 0, double disc_rate = 0.0):ItemBase(book, sales_price), min_qty(qty), discount(disc_rate){}
    double net_price(size_t cnt) const  //重做虚函数
    {
        if (cnt >= min_qty)
            return cnt * (1 - discount) * price;
        else
            return cnt * price;
    }
private:
    size_t min_qty;
    double discount;
};

int main()
{
    ItemBase item("0-12", 9.9);
    std::cout << item.book() << "," << item.net_price(10) << std::endl;  //0-12,99
    BulkItem item2("0-12", 9.9, 10, 0.12);
    std::cout << item2.book() << "," << item2.net_price(10) << std::endl;  //0-12,87.12

    system("pause");
    return 0;
}

26、动态绑定

涉及知识点:
1、多态性;
2、从派生类到基类的转换;
3、引用或指针即可以指向基类对象,也可以指向派生类对象;
4、只有通过引用或指针调用虚函数才会发生动态绑定;
5、一个基类指针,既可以指向基类对象也可以指向派生类对象;
6、能够接受基类对象的形参,都可以传派生类对象,如果传基类对象,在调用时会使用基类对象的对应成员函数,如果传为派生类,会调用派生类中定义的对应成员函数(由虚函数重写而来的),这叫动态绑定;

例如:

class ItemBase  //基类,原价卖书
{
public:
    ItemBase(const std::string &book = " ", double sales_price = 0.0):isbn(book), price(sales_price){}
    std::string book(){return isbn;}
    virtual double net_price(size_t n) const{return n * price;}  //虚函数在子类中可以重做,而普通函数不行
protected:
    double price;
private:
    std::string isbn;
};

class BulkItem : public ItemBase  //派生类,打折卖书
{
public:
    //qty为最低打折数量,默认为9,折扣值为0;派生类调用基类构造函数初始化基类
    BulkItem(const std::string &book = " ", double sales_price = 0.0, size_t qty = 0, double disc_rate = 0.0):ItemBase(book, sales_price), min_qty(qty), discount(disc_rate){}
    double net_price(size_t cnt) const  //重做虚函数
    {
        if (cnt >= min_qty)
            return cnt * (1 - discount) * price;
        else
            return cnt * price;
    }
private:
    size_t min_qty;
    double discount;
};

void printTotal(std::ostream &os, ItemBase *item, size_t n)  //item传指针或引用都可以
{
    os << "Isbn:" << item->book() << "\tnumber sold:" << n << "\total price:" << item->net_price(n) << std::endl;
}

int main()
{
    ItemBase* item1 = new ItemBase("1-234", 11.0);
    BulkItem* item2 = new BulkItem("1-234", 22.0, 2, 0.05);
    printTotal(std::cout, item1, 2);  //Isbn:1-234      number sold:2   otal price:22
    printTotal(std::cout, item2, 3);  //Isbn:1-234      number sold:3   otal price:62.7

    system("pause");
    return 0;
}

27、三种继承

涉及知识点:
1、公有、私有、受保护继承:
class B : public A最常用,以下两种不会用到;
class B : private A把基类A中继承来的成员,都变成B中私有的成员,不能再继承下去
class B : protected A把基类A中集成的成员,都变成B中受保护的成员,还能继承下去,但在类外不能用;
2、接口继承和实现继承;
3、修改继承访问,去除个别成员;
4、默认继承访问级别;
5、私有、受保护继承叫做实现继承,公有继承叫做接口继承

修改继承访问方式,例如:

class B : private A
{
public:
    using A::a1;  //假设a1为A中公有成员,使用这种修改继承访问方式,可使a1变回公有
    ···

其他例子:

class B : A  //什么都不写,默认为private继承
struct B : A  //结构体继承,默认为public继承

28、派生类的构造函数和析构函数

构造函数和析构函数是不能被继承的。
涉及知识点:
1、派生类的构造函数执行顺序:先执行基类的构造函数,再执行成员对象的构造函数,最后执行派生类的构造函数;
2、派生类的析构函数执行顺序:先对派生类新增的普通成员进行清理,在调用成员对象的析构函数,最后调用基类析构函数;
3、在创建派生类对象时,必须先创建基类,实现方式是派生类构造函数需要调用基类构造函数

例如:

class Base1
{
public:
    Base1(int i) {b1 = i; std::cout << "Base1的构造函数被调用。" << std::endl;}
    void print() {std::cout << b1 << std::endl;}
private:
    int b1;
};

class Base2
{
public:
    Base2(int i) {b2 = i; std::cout << "Base2的构造函数被调用。" << std::endl;}
    void print() {std::cout << b2 << std::endl;}
private:
    int b2;
};

class Base3
{
public:
    Base3() {b3 = 0; std::cout << "Base3缺省的构造函数被调用。" << std::endl;}
    void print() {std::cout << b3 << std::endl;}
private:
    int b3;
};

class Member
{
public:
    Member(int i) {m = i; std::cout << "Member的构造函数被调用。" << std::endl;}
    int getM()    {return m;}
private:
    int m;
};

class Derived : public Base2, public Base1, public Base3
{
public:
    Derived(int i, int j, int k, int l);
    void print();
private:
    int d;
    Member men;  //Member类的的对象
};

Derived::Derived(int i, int j, int k, int l):Base1(i), Base2(j), men(k)  //初始化顺序:Base2、Base1、Member、Derived,如果不写初始化列表,会调用基类默认构造函数。
{
    d = l;
}

void Derived::print()
{
    Base1::print();  //派生类可直接调用基类成员函数
    Base2::print();
    Base3::print();
    std::cout << men.getM() << std::endl;
    std::cout << d << std::endl;
}

int main()
{
    Derived der(1, 2, 3, 4);
    der.print();
    system("pause");
    return 0;
}

29、转换与继承

涉及知识点:
1、派生类 -> 基类,派生类转换为基类是自动的,反之是强制的。引用转换(包含指针转换),对象转换;
2、基类 -> 派生类,基类到派生类的自动转换不存在强制转换;
3、使用引用转换和指针转换可以动态绑定,而对象转换则使用基类成员函数,类似26节中的例子,若传派生类对象,则"图书不打折";

引用25节中的例子,例如:

item2 = item;  //错误
BulkItem *p = &item;  //错误
BulkItem *p = static_cast<BulkItem *>(&item);  //正确,指针或引用强制转换可以
BulkItem p = static_cast<BulkItem>(item);  //错误,对象强制转换不行
p->net_price(10);  //得到99

30、友元与继承

涉及知识点:
1、友元可以访问类的private和protected成员;
2、友元关系不能继承,要明确授予友元;

例如:

class Base
{
    friend class Frnd;
protected:
    int i;
};

class D1 : public Base
{
private:
    int j;
};

class Frnd
{
public:
    int men(Base b){return b.i;}
    int mem(D1 d){return d.i;}
    //int men(D1 d){return d.j;}  //错误,Frnd不是D1的友元,不能访问其私有成员
};

class D2 : public Frnd
{
public:
    //int mem(Base b){return b.i;}  //错误,友元关系不能被D2继承
    //int men(D1 d){return d.j;}  //错误
};

31、静态成员与继承

涉及知识点:
1、积累中的static成员,在整个继承层次中只有一个实例;
2、在派生类中访问static成员方法,成员函数也可以;
3、调用方式:基类名::成员名子类名::成员名对象::成员名指针->成员名

例如:

class A
{
public:
    static std::size_t object_count(){return 100;}
protected:
    static const std::size_t obj_count = 99;
};

class B : public A
{
public:
    void f(const B &b, B *b2)
    {
        std::cout << A::obj_count << std::endl;
        std::cout << B::obj_count << std::endl;
        std::cout << b.obj_count << std::endl;
        std::cout << b2->obj_count << std::endl;
        std::cout << obj_count << std::endl;
        std::cout << A::object_count() << std::endl;
        std::cout << B::object_count() << std::endl;
    }
};

32、纯虚函数与抽象类

涉及知识点:
1、纯虚函数:只能够用来继承的,在继承的时候必须进行覆盖;
2、抽象类–抽象数据类型:

  • 任何包含一个或多个纯虚函数的类都是抽象类;
  • 不要创建这个类的对象,应该继承它;
  • 务必覆盖从这个类继承的纯虚函数,如果没有覆盖所有纯虚函数,那么该子类还是抽象类;

3、实现纯虚函数;
4、C++接口:就只是包含纯虚函数的抽象基类,构造、析构函数不算;
5、类中有继承得到的虚函数时,必须写一个虚析构函数;

例如下面几个类(继承关系class Circle -> class Shape <- class Rctangle <- class Square):

class Shape
{
public:
    Shape(){}
    virtual ~Shape(){}
    virtual double GetArea() = 0;  //纯虚函数
    virtual double GetPerim() = 0;
    virtual void Draw() = 0;
};

//可以不写纯虚函数的实现,写了的话就会写一些对继承了以后子类中的有可能会用到的一些通用的方法
void Shape::Draw() {std::cout << "Shape::Draw()方法" << std::endl;}

class Circle : public Shape
{
public:
    Circle(int radius) : itsRadius(radius){}
    virtual ~Circle(){}  //写一个虚析构函数防止出错
    //关于以下三行,若纯虚函数没有重写完,那么shape也会变为抽象类,子类若全部覆盖,那么孙类就可以不用全覆盖,不会变为抽象类
    double GetArea(){return 3.14 * itsRadius * itsRadius;}
    double GetPerim(){return 2 * 3.14 * itsRadius;}
    void Draw();
private:
    int itsRadius;
};

void Circle::Draw()
{
    std::cout << "Circle::Draw()方法" << std::endl;
    Shape::Draw();  //都可以在每个子类中去掉用通用的方法
}

class Rectangle : public Shape
{
public:
    Rectangle(int len, int width):itsWidth(width), itsLength(len){}
    virtual ~Rectangle(){}
    double GetArea(){return itsLength * itsWidth;}  //因为GetArea本来就是纯虚函数,被继承下来也会是纯虚函数,前面写个virtual也可以
    double GetPerim(){return 2 * itsWidth + 2 * itsLength;}
    virtual int GetLength(){return itsLength;}  //要做继承最好写虚函数。规则:只要一个类里任何一个函数是虚函数,那么最好所有函数都是虚函数,但构造函数不能是虚函数
    virtual int GetWidth(){return itsWidth;}
    void Draw();
private:
    int itsWidth;
    int itsLength;
};

void Rectangle::Draw()
{
    for (int i = 0; i < itsLength; i++)
    {
        for (int j = 0; j < itsWidth; j++)
            std::cout << "X";
        std::cout << std::endl;
    }
    Shape::Draw();
}

class Square : public Rectangle
{
public:
    Square(int len);
    Square(int len, int width);
    virtual ~Square(){}
    double GetPerim(){return 4 * GetLength();}
};

Square::Square(int len) : Rectangle(len, len){}
Square::Square(int len, int width) : Rectangle(len, width)
{
    if (GetLength() != GetWidth())
        std::cout << "Error, not a square... a rectangle?" << std::endl;
}

int main()
{
    int choice;
    bool fQuit = false;
    Shape *sp;
    while (fQuit == false)
    {
        std::cout << "(1)Circle (2)Rectangle (3)Square (0)Quit :";
        std::cin >> choice;
        switch(choice)
        {
            case 1: sp = new Circle(5); break;  //指针就必须new,然后调用构造函数
            case 2: sp = new Rectangle(4, 6); break;
            case 3: sp = new Square(5); break;
            case 0: fQuit = true; break;
        }
        if (fQuit == false)
        {
            sp->Draw();  //体现了多态
            delete sp;  //有new就delete一次
            std::cout << std::endl;
        }
    }

    system("pause");
    return 0;
}

33、模板与泛型编程

涉及知识点:
1、两种模板:类模板、函数模板;
2、泛型编程:主要用于容器、迭代器、算法(如C++的STL标准模板库);
3、示例:

  • 普通队列
  • C++中的泛型容器
  • 顺序队列
  • 链式队列

例如,类模板:

template<class T>  //注意:每个成员函数的实现部分也要加上template
class Queue
{
public:
    Queue();
private:
    T element;  //会用指定的类型去建立element
};

int main()
{
    Queue<int> a;
    Queue<std::string> b;

    system("pause");
    return 0;
}

36、函数模板

涉及知识点:
1、函数模板 -> 实例化 -> 函数;
2、模板形参:templatetemplate

例如:

template<typename T>  //注意:每个成员函数的实现部分也要加上template
const T& bigger(const T& v1, const T& v2)  //因为v1、v2都为const,所以返回值必须为const
{
    return v1 > v2 ? v1 : v2;
}

template <typename T1, typename T2>
T1& print(T1 &s, T2 val)
{
    s << val;
    return s;
}

int main()
{
    double a = 1.23, b = 5.89;
    std::cout << bigger(a, b) << std::endl;
    std::string s1("hi"), s2("world");
    std::cout << bigger(s1, s2) << std::endl;

    std::string oristr = "this is a test";
    print(std::cout, oristr) << std::endl;

    std::ofstream outFile("file.txt", std::ofstream::out);
    print(outFile, oristr) << std::endl;
    outFile.close();

    system("pause");
    return 0;
}

38、异常层次结构

涉及知识点:
1、异常是类–创建自己的异常类;
2、异常派生;
3、异常中的数据–数据成员;
4、按引用传递异常;
5、在异常中使用虚函数;

例如:

const int DefaultSize = 10;
class Array
{
public:
    Array(int size = DefaultSize);
    ~Array(){delete[] PType;}
    int GetitSize() const {return itsSize;}  //访问器
    //运算符重载
    int& operator[](int offset);
    const int& operator[](int offset) const;

    class xBoundary{};
    class Xsize
    {
    public:
        Xsize(){}
        Xsize(int size):itsSize(size){}
        ~Xsize(){}
        int GetitSize(){return itsSize;}
        virtual void PrintError(){std::cout << "下标发生错误:" << itsSize << std::endl;}
    protected:
        int itsSize;
    };

    class Xzero : public Xsize
    {
    public:
        Xzero(int size):Xsize(size){}
        virtual void PrintError(){std::cout << "下标不能是0!" << std::endl;}
    };

    class Xnegative : public Xsize
    {
    public:
        Xnegative(int size):Xsize(size){}
        virtual void PrintError(){std::cout << "下标不能是负数:" << Xsize::itsSize << std::endl;}
    };

private:
    int *PType;
    int itsSize;
};

Array::Array(int size):itsSize(size)
{
    if (size == 0)
        throw Xzero(size);
    if (size < 0)
        throw Xnegative(size);
    PType = new int[size];
    for (int i = 0; i < size; i++)
        PType[i] = 0;
}

int& Array::operator[](int offset)
{
    int size = this->GetitSize();
    if (offset >= 0 && offset < size)
        return PType[offset];
    throw xBoundary();
}

const int& Array::operator[](int offset) const
{
    int size = this->GetitSize();
    if(offset >= 0 && offset < size)
        return PType[offset];
    throw xBoundary();
}

int main()
{
    try
    {
        Array b(6);
        Array c(0);
    }
    catch (Array::Xsize& exp)  //捕获异常,exp只能是引用或指针
    {
        exp.PrintError();
    }
    catch (...)
    {
        std::cout << "发生未知异常" << std::endl;
    }

    system("pause");
    return 0;
}

40、智能指针、auto_ptr类

涉及知识点:
1、常规指针,容易产生内存泄漏;
2、智能指针:

  • 自己动手设计智能指针很难,考虑深度复制、写时复制、引用计数、引用链接、破坏性复制;
  • 使用std::auto_ptr智能指针,基于破坏性复制的指针,用于简单环境;
  • 使用Boost智能指针;
  • 使用ATL框架中的智能指针,如CComPtrCComQIPtr等等;

例如:

class Dog
{
public:
    operator int() const {return age;}
private:
    int age;
};

void demo()
{
    std::auto_ptr<double> pd(new double);  //例子1
    *pd = 28.5;
    std::cout << *pd << std::endl;
}

int main()
{
    demo();
    std::auto_ptr<Dog> pDog(new Dog());  //例子2

    system("pause");
    return 0;
}

41、命名空间

涉及知识点:
1、命名空间的定义

  • 每个命名空间是一个作用域
  • 命名空间可以是不连续的
  • 接口和实现的分离
  • 嵌套命名空间

2、命名空间成员的使用
注意:

  • 名称空间在.h文件里划分好函数声明后,在函数实现的cpp文件里也要划分好;
  • 名称空间可以不连续的命名,C++会自动把它们关联在一起;
  • 名称空间可以嵌套使用,如MyApp::Sal::Dog D;
  • 头文件中不能使用using namespace
  • cpp文件中可以使用using std::cout;来简化某个名称的写法,比using namespace std;好,可以不导入全部的std
  • 写在全局的类或变量为全局名称空间,可以用::fun();来调用;

例如:

using std::vector;
namespace s = std;  //可将复杂的名称空间简化
using namespace std;

namespace MyApp
{
    double pail;
    void processTrans()
    {
        BookStore::Sales_item s;
        bs::Sales_itme s2;
    }
    namespace sal
    {
        class Dog{};
    }
}

42、多继承与虚基类

涉及知识点:
1、多继承(被JAVA、C#语言取消了);
2、多继承中的二义性问题(使用虚基类技术);
3、虚基类,又叫虚继承,菱形多继承产生的二义性问题;
4、虚基类的构造函数(已证明单继承可以适用各种程序设计,可以不用多继承)

1、存在二义性问题,例如:

class A
{
public:
    A(int age):itsAge(age){}
    virtual ~A(){}
    virtual int GetAge(){return itsAge;}
    virtual void SetAge(int age){itsAge = age;}
private:
    int itsAge;
};

class H : public A
{
public:
    H(int age):A(age){}
    virtual ~H(){}
};

class B : public A
{
public:
    B(int age):A(age){}
    virtual ~B(){}
};

class P :public H, public B  //多继承
{
public:
    P(int age):H(age), B(age){}
    ~P(){}
};

int main()
{
    P *peg = new P(2);
    //peg->GetAge();  //错误,因为由B、H中得到两个GetAge,且都是从A中来的
    peg->H::GetAge();
    peg->B::GetAge();

    system("pause");
    return 0;
}

2、解决二义性问题,例如:

class A
{
public:
    A(int age):itsAge(age){}
    virtual ~A(){}
    virtual int GetAge(){return itsAge;}
    virtual void SetAge(int age){itsAge = age;}
private:
    int itsAge;
};

class H : virtual public A  //虚继承,这时候P类的A就会只有1个,而不会像例1中有两个
{
public:
    H(int age):A(age){}
    virtual ~H(){}
};

class B : virtual public A
{
public:
    B(int age):A(age){}
    virtual ~B(){}
};

class P :public H, public B  //多继承
{
public:
    P(int age):H(age), B(age), A(age){}  //需要调用虚基类构造函数,此时H、B就不去构造A了
    ~P(){}
};

int main()
{
    P *peg = new P(2);
    peg->GetAge();  //正确

    system("pause");
    return 0;
}

43、特殊工具与技术

涉及知识点:
1、allocator类,类似new,用于分配内存;
例如:

std::allocator<Sales_item> a;
a.allocate(100);  //分配指定大小的内存

2、RTTI运行时对象类型识别,再不能够使用虚函数是使用;
例如:

ItemBase *bp = new ItemBase();  //基类指针
BulkItem *dp = new BulkItem();  //派生类指针
BulkItem *dp2 = dynamic_cast<BulkItem*>(bp);
if (typeid(BulkItem) == typeid(*dp))  //typeid可以用来检查类型
    std::cout << "BulkItem类型" << std::endl;
std::cout << typeid(*bp).name() << std::endl;  //也可以输出类名

3、类成员的指针;
例如:

std::string ItemBase::*p = &ItemBase::isbn;  //指向ItemBase中string类型的一个public成员
double(ItemBase::*pmf)(std::size_t) const = &ItemBase::net_price;  //指向函数的指针,net_price返回值为double的const函数,形参为std::size_t类型

4、嵌套类,在类中写一个类,类似于类中的异常类写法,如第38节;
5、union联合(共用体);
例如:

union TokenValve  //联合,重叠在一起,只有一个最大成员的长度,本例长度8字节(double)
{
    char cval;  //占1字节■□□□□□□□
    int ival;  //占4字节■■■■□□□□
    double dval;  //占8字节,最大■■■■■■■■
};

int main()
{
    TokenValve mytoken = {'a'};  //给cval赋值
    std::cout << mytoken.ival << std::endl;  //打印97

    system("pause");
    return 0;
}

6、局部类,累在函数内,也只能在这个函数里使用;
例如:

void fun()
{
    class Cat{};
    Cat cat;  //该类只能在函数中使用
}

7、位域;
例如:

typedef unsigned int Bit;  //int有四字节,32位
class File
{
public:
    Bit mode:2;  //用着32位中的2位给mode变量
    Bit modified:1;  //1位给modified变量,和普通成员一样用,但是要注意溢出
};

8、易失的volatile
例如:

volatile int y;  //定义变量时用它告诉C++不要在编译优化时改变变量y在内存中的位置

9、extern "C"
例如:

extern "C" void show(char *);  //告诉C++在另一个文件中有一个C代码的函数show()来声明这个函数

你可能感兴趣的:(C++)