c++
一、 概述
C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计,因而C++就适应的问题规模而论,大小由之。C++不仅拥有计算机高效运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。
二、 基本语法同c的区别
1. <</>>
左移或右移操作符,做了功能的改造。这种改造成为语言操作符重载。表示输出或输入。
2. 头文件和命名空间
支持c的头文件。比如<stdio.h>,如果是c和c++共用的,写成c的格式。支持cpp的头文件,比如<iostream>。支持hpp的头文件。
命名空间可以有效避免大型项目中的各种名称冲突。usingnamespace std;
自定义命名空间。
namespace myns {
void func(int i){
cout<< "my ns" << endl;
}
}
namespace myns2 {
void func(int i){
cout<< "22 my ns" << endl;
}
}
using namespace myns2;
int main(){
func(10);
myns::func(2);
}
对于匿名命名空间,就是没有名字的命名空间,其中的函数只能在内部使用。匿名命名空间很少被使用。
3. 新增bool类型
c++中的bool可取的值只有true和false。
4. const和volatile
const声明常量。c语言中const是假常量,c语言中const变量是只读变量,有自己的存储空间。c++中是真正的常量,可能分配存储空间,也可能不分配存储空间,当const常量为全局,并且需要在其他文件中使用,当使用&操作符取const常量的地址。
c++的const常量类似于#define。不同是,const常量是偶编译器处理的,提供类型检查和作用域检查;宏定义由预处理器处理,单纯的文本替换。
volatile,放弃优化。
5. 强制转化
指针类型,必须强制转化。
6. struct类型加强
c语言的struct定义了一组变量的集合,c编译器并不认为这是一种新的类型。c++中的struct是一个新类型的定义声明。c++中,struct和class关键字完成的功能有类似之处。
struct Student
{
charname[100];
intage;
};
void main()
{
Students1 = {"stu1", 12};
Students2 = {"stu2", 15};
cout<< s1.age << endl;
cout<< s2.name << endl;
system("pause");
}
7. 三目运算符增强
c语言返回的是值,不能做为左值。c++返回的是变量本身,可以出现在程序的任何地方。
8. 内存分配
c语言中使用malloc等函数。c++中使用new关键字。malloc使用free函数释放。new分配的内存使用delete释放。
9. 引用
在c++中新增加了引用的概念。引用可以看作一个已定义变量的别名。引用的语法,Type & name = var;。引用做函数参数声明时不进行初始化。
void main()
{
int a =10;
int&b = a;
cout<< b << endl;
b = 23;
cout<< a << endl;
system("pause");
}
在c语言中,交换两个变量的值的函数需要传递函数的地址。c++中传引用就可以。&a,&b。引用就是一个变量的别名而不是地址。引用的地址和变量的地址是一样的。
引用作为其他变量的别名而存在,在一些场合可以代替指针。引用相对于指针来说具有更好的可读性和实用性。
引用占4个字节的内存。引用在c++中其实就是一个常量指针。c++编译器在编译过程中使用常量指针作为引用的内部实现,因此引用所占的空间大小与指针相同。
常引用,使变量引用只读。不能通过引用去修改被引用的变量。常引用初始化分两种情况,一是用变量初始化;二是用字面量初始化,const int &a = 10。
10. 函数返回值为引用
当函数返回值为引用时,若返回栈变量,不能成为其他引用的初始值,不能作为左值使用。若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用。
11. 内联函数
inline。内联函数不直接调用函数,而是将内联函数的代码嵌入到调用的语句中。内联函数适合代码很少,并且频繁而大量的调用。内联函数省去了将函数压栈,跳转和返回的开销。内联函数中不能存在任何形式的循环语句,不能存在过多的条件判断。
12. 缺省参数和占位参数
c++在定义函数的提供缺省参数,如果在调用函数的时候没有提供实际参数,那么实参的值就是定义函数时的形参的缺省值。定义缺省的规则是不可以前面的参数有缺省值而后面的参数没有,就是说缺省参数右边有参数的话也要是缺省参数。
占位参数,只有参数类型声明,没有参数名声明,一般情况下,在函数体内无法使用占位参数。调用有占位参数的函数时,需要写够参数。
可以将占位参数和默认参数结合起来使用,意义是为以后程序的扩展留下线索,兼容c语言程序可能出现的不规范写法。
int func1(int a,int b,int = 0)
{
returna + b;
}
void main()
{
func1(2,3);
func1(2,3, 0);
system("pause");
}
13. 函数重载
重载,同名而参数列表不同,参数的类型或者个数不同。
当函数重载的参数为缺省参数,则需要看情况,可能成功也可能不成功。存在二义性的重载会失败,比如:
int func2(int a)
{
returna;
}
int func2(int a, int b)
{
returna + b;
}
int func2(int a, int b, int c = 3)
{
returna + b;
}
void main()
{
func2(1);
//func2(1,3);
func2(1,3, 4);
}
函数重载与函数指针,当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者与函数指针的函数类型。
14. 函数模版
通过类似java中的泛型的规则,在使用函数时确定函数参数的类型。
template<classT>
Tadd(Ta,Tb){
returna+b;
}
intmain()
{
inta=1;
intb=2;
cout<<add(a,b)<<endl;
return0;
}
模版函数也可以重载,参数的数量不同。
三、 类
1. 类的本质
类其实就是结构的数据成员加可执行代码,统一提供封装,继承,多态。
在类内部,没有权限限定符,默认是private。
在结构内部,没有权限限定符,默认是public。
2. 类的封装
两层含义,把类的属性和方法进行封装,对属性和方法进行访问控制。public修饰的,可以在类的内部和类的外部访问;private修饰的,只能在类的内部访问,不能在类的外部访问;protected修饰的,只能在类的内部和继承的类的中被访问,不能在类的外部和非继承关系的类或文件中访问。
3. 类对成员的保护
如果类函数返回的是成员变量的指针,为了避免在类外部成员变量被修改,所以函数就要返回常量指针。
如果一个类成员变量和一个全局变量重名,那么在类成员函数当中默认访问的是类的成员变量。
在类的内部访问全局标识,关键字::
4. struct和class的区别
struct,成员的默认访问权限是public。class,成员的默认访问权限是private。
5. 类的声明和类的实现分开
使用IDE的添加类功能,添加一个类。
Person.h文件
#pragma once
class Person
{
private:
int age;
char *name;
public:
void setAge(int age);
int getAge();
void setName(char *name);
char *getName();
Person();
~Person();
};
#prama once只包含一次。
#include "Person.h"
void Person::setAge(int age)
{
this->age = age;
}
int Person::getAge()
{
return age;
}
void Person::setName(char *name)
{
this->name = name;
}
char * Person::getName()
{
return name;
}
Person::Person()
{
}
Person::~Person()
{
}
测试
#include "Person.h"
void main()
{
Person p1;
p1.setAge(10);
cout << p1.getAge()<< endl;
system("pause");
}
6. 类的作用域
类成员变量作用域限于类内部,类的外部是不可见的。
7. 类的构造函数和析构函数
类的构造函数名称和类的名称一致,而且没有返回值。一个类实例化为一个对象的时候,会自动调用构造函数。一个对象在销毁的时候会自动调用析构函数。
构造函数的初始化成员列表,初始化成员列表只能在构造函数中使用。const成员只能在初始化成员列表赋值。引用数据成员必须初始化成员列表赋值。
c++中的类可以定义一个特殊的成员函数清理对象,就是析构函数。语法,~ClassName();。
由于析构函数只有一个,所以在不同的构造函数里面给函数的成员指针分配内存的时候,一定要统一new或者new[]。
class Test1
{
private:
int a;
int b;
public:
Test1()
{
cout <<"无惨构造" << endl;
}
Test1(int a)
{
cout <<"一个参数构造" << endl;
}
Test1(int a, int b)
{
cout <<"有参构造" << endl;
}
Test1(const Test1 &t)
{
cout <<"copy构造" << endl;
}
};
Test1 getTest1()
{
Test1 t(1, 3);
return t;
}
void main()
{
//c++编译器自动调用无惨构造
Test1 t1;
//c++编译器自动调用有参构造
Test1 t2(1, 3);
Test1 t3 = (1, 5);
Test1 t4 = 1;
//c++编译器自动构造copy构造
Test1 t5 = t2;
Test1 t6(t2);
Test1 t7 = getTest1();
system("pause");
}
8. 浅拷贝和深拷贝
拷贝构造函数,浅拷贝,两个对象成员变量简单的赋值。
深拷贝,不同的对象指针成员指向不同的内存地址,拷贝构造的时候不是简单的指针赋值,而是将内存拷贝过来。如果类成员有指针,那么需要自己实现拷贝构造函数,不然存在浅拷贝的风险。
9. 构造函数的初始化列表
在构造函数中为类的成员变量初始化赋值。
class Test2
{
private:
int a;
int b;
public:
Test2(int a, int b) :a(a),b(b)
{
cout <<"构造函数的初始化列表" << endl;
}
int getA()
{
return a;
}
};
void main()
{
Test2 test2(1, 2);
cout << test2.getA()<< endl;
system("pause");
}
10. 声明了const的类
如果声明了const类,const man m,如果使用了这个类的成员。那么,在类的定义中,需要将这个或这些成员定义为const的。比如:
int main()
{
const man m;
m.name;
man m2("hello");
return 0;
}
需要在类的声明中,声明name为const。
11. const修饰成员函数时
const修饰成员函数时,修饰的this指针指向的内存空间。
12. 函数参数是一个类
如果函数的参数是一个类,在函数中修改类的成员,在参数中就使用类的引用。如果不想参数类在函数中被修改,那么在参数中使用const &。
13. 对象的动态建立和释放
在软件开发过程中,常常需要动态地分配和撤销内存空间,例如动态链表中结点的插入与删除。在c语言中利用库函数malloc和free来分配和撤销内存空间。在c++中,提供了较简便而功能较强的new和delete运算符来取代malloc和free。
一般格式,new运算符动态分配堆内存,指针变量=new 类型(常量);指针变量=new 类型[表达式];,作用是从堆内存分配一块类型大小的存储空间,返回首地址。其中,常量是初始化值,可缺省。创建数组对象时,不能为对象指定初始值。delete运算符释放已分配的内存空间,delete 指针变量;delete []指针变量;。其中,指针变量必须是new返回的一个指针。
void main()
{
int *p1 = (int*)malloc(sizeof(int)* 10);
p1[0] = 1;
p1[1] = 2;
free(p1);
int *p2 = new int[10];
p2[0] = 1;
delete []p2;
system("pause");
}
void main3()
{
int *p = (int*)malloc(sizeof(int));
*p = 10;
free(p);
int *p2 = new int;
*p2 = 10;
delete p2;
int *p3 = new int(30);
cout << *p3 <<endl;
delete p3;
system("pause");
}
malloc、free和new、delete可以交叉使用,但是c语言的malloc和free在操作类时不会调用其构造函数和析构函数。
14. explicit
告诉c++编译器,要明确的调用这个构造函数,而不是=操作符是要调用构造的。
15. this指针
this关键字表示当前类的对象的指针。
man::man(const char *name)
{
this->name = name;
cout << name <<endl;
}
16. static静态修饰符。
将变量或数据放到静态内存区,程序加载就存在,直到程序退出才清理。
类的静态成员变量初始化,需要在类的构造方法或方法外初始化。类的静态成员和类的实例化对象没有直接关系,类的静态成员放在静态内存区,是程序开始就存在,一直到程序结束才结束。可以直接使用类名,调用静态成员。
17. 友元函数
friend修饰的函数是友元函数。友元函数可以修改类的私有属性,破坏了类的封装性。友元函数声明位置与访问符无关。
class A
{
public:
friend void modifyA(A *p);
A(int a, int b)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
private:
int a;
int b;
};
void modifyA(A *p)
{
p->a = 100;
}
void main()
{
A a(2, 3);
modifyA(&a);
cout << a.getA()<< endl;
system("pause");
}
18. 友元类
若B类是A类的友元类,则B类的所有成员函数都是A类的友元函数。友元类通常设计为一种数据操作或类之间传递消息的辅助类。
设计友元类的意义,1,java在jdk编译为.class字节码文件,反射机制分析.class找到类对象,直接修改类的私有属性。反射机制成为一种标准。2,.cpp文件编译为汇编,由汇编往回找类的私有属性时,需要破坏类的封装性比较容易。友元函数是这样的一个后门。
class A
{
friend class B;
public:
friend void modifyA(A *p);
A()
{
}
A(int a, int b)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
void display()
{
cout << b<< endl;
}
private:
int a;
int b;
};
class B
{
public:
B()
{
}
void setA(int a)
{
Ao.b = a;
}
void display()
{
Ao.display();
}
private:
A Ao;
};
void modifyA(A *p)
{
p->a = 100;
}
void main()
{
B b;
b.setA(10);
b.display();
system("pause");
}
四、 运算符重载
运算符重载使得用户自定的数据以一种更简洁的方式工作。运算符重载是一种特殊的成员函数或友元函数。
比如全局函数完成+操作符重载
Complex operator+(Complex &c1,Complex &c2);
类成员完成-操作符重载
Complex operator-(Complex &c2);
1. 重载()运算符
class F
{
public:
int operator() (int a, intb)
{
return a*a +b*b;
}
};
void main()
{
F f;
int a = f(2, 4);
cout << a <<endl;
system("pause");
}
2. 为什么补充在&&和||操作符
他们是c++中非常特殊的操作符;内置实现了短路操作;操作符重载是靠函数重载来完成的;操作数作为函数参数传递;c++的函数参数都会被求值,无法实现短路规则。
3. 实现一个字符串类
MyString.h头文件
#pragma once
#include <iostream>
using namespace std;
class MyString
{
//重载<<、>>,定义为友元函数,以便使用私有变量
friend ostream &operator<<(ostream &out, MyString &ms);
friend istream &operator>>(istream &in, MyString &ms);
public:
MyString();
MyString(int len);
MyString(const char *str);
MyString(const MyString& ms);
~MyString();
public:
//重载=
MyString &operator=(const char *str);
MyString &operator=(const MyString &ms);
//重载[]
char & operator[](intindex);
//重载== !=
bool operator==(const char*str);
bool operator==(constMyString &ms);
bool operator!=(const char*str);
bool operator!=(constMyString &ms);
//重载<、>
int operator>(constchar *str);
int operator>(constMyString &ms);
int operator<(constchar *str);
int operator<(constMyString &ms);
//把类的指针露出来
char *c_str()
{
return m_str;
}
const char * c_str2()
{
return m_str;
}
private:
int m_len;
char *m_str;
};
MyString.cpp文件
#define _CRT_SECURE_NO_WARNINGS
#include "MyString.h"
//重载<<
ostream & operator<<(ostream &out, MyString &ms)
{
out << ms.m_str;
return out;
}
//重载>>
istream & operator>>(istream &in, MyString &ms)
{
cin >> ms.m_str;
return in;
}
MyString::MyString()
{
m_len = 0;
m_str = new char[m_len +1];
strcpy(m_str,"");
}
MyString::MyString(int len)
{
if (len == 0)
{
m_len = 0;
m_str = newchar[m_len + 1];
strcpy(m_str,"");
}
else
{
m_len = len;
m_str = newchar[m_len + 1];
memset(m_str, 0,m_len);
}
}
MyString::MyString(const char *str)
{
cout << "有参构造" << endl;
if (str == NULL)
{
m_len = 0;
m_str = newchar[m_len + 1];
strcpy(m_str,"");
}
else
{
m_len =strlen(str);
m_str = newchar[m_len + 1];
strcpy(m_str,str);
}
}
MyString::MyString(const MyString & ms)
{
cout << "拷贝构造函数" << endl;
if (ms.m_len == 0)
{
m_len = 0;
m_str = newchar[m_len + 1];
strcpy(m_str,"");
}
else
{
m_len =ms.m_len;
m_str = newchar[m_len + 1];
strcpy(m_str,ms.m_str);
}
}
MyString::~MyString()
{
if (m_str != NULL)
{
delete[] m_str;
m_str = NULL;
m_len = 0;
}
}
//重载=
MyString & MyString::operator=(const char *str)
{
//先判断是否需要释放旧的内存
if (m_str != NULL)
{
delete[] m_str;
m_len = 0;
}
//判断参数是否为空
if (str != NULL)
{
m_len =strlen(str);
m_str = newchar[m_len + 1];
strcpy(m_str,str);
}
else
{
m_len = 0;
m_str = newchar[1];
strcpy(m_str,"");
}
return *this;
}
MyString & MyString::operator=(const MyString &ms)
{
//先判断是否需要释放旧的内存
if (ms.m_str != NULL)
{
delete[] m_str;
m_len = 0;
}
//判断参数是否为空
if (ms.m_str != NULL)
{
m_len =strlen(ms.m_str);
m_str = newchar[m_len + 1];
strcpy(m_str,ms.m_str);
}
else
{
m_len = 0;
m_str = newchar[1];
strcpy(m_str,"");
}
return *this;
}
//重载[]
char & MyString::operator[](int index)
{
return m_str[index];
}
//重载== !=
bool MyString::operator==(const char *str)
{
if (str == NULL)
{
if (m_len == 0)
{
returntrue;
}
return false;
}
else
{
if (m_len ==strlen(str))
{
return!strcmp(m_str, str);
}
return false;
}
}
bool MyString::operator!=(const char *str)
{
return !(*this == str);
}
bool MyString::operator==(const MyString &ms)
{
if (m_len != ms.m_len)
{
return false;
}
else
{
return!strcmp(m_str, ms.m_str);
}
}
bool MyString::operator!=(const MyString &ms)
{
return !(*this == ms);
}
//重载<、>
int MyString::operator>(const char *str)
{
return strcmp(str, m_str);
}
int MyString::operator>(const MyString &ms)
{
return strcmp(ms.m_str,m_str);
}
int MyString::operator<(const char *str)
{
return strcmp(m_str, str);
}
int MyString::operator<(const MyString &ms)
{
return strcmp(m_str,ms.m_str);
}
测试
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
#include "MyString.h"
using namespace std;
void main()
{
//MyString m1();
MyString m2("有参");
MyString m3 = m2;
MyString m4 ="ss";
m4 = m3;
m4 = "1234";
m4[0] = 'h';
cout << m4[0]<< endl;
cout << m4 <<endl;
if (m4 =="h123")
{
cout <<"相等" << endl;
}
else
{
cout <<"不相等" << endl;
}
if (m3 == m2)
{
cout <<"相等" << endl;
}
else
{
cout <<"不相等" << endl;
}
if (m3 < "sss")
{
cout <<"小于" << endl;
}
else
{
cout <<"不小于" << endl;
}
if (m3 < m2)
{
cout <<"小于" << endl;
}
else
{
cout <<"不小于" << endl;
}
if (m3 >"sss")
{
cout <<"大于" << endl;
}
else
{
cout <<"不大于" << endl;
}
if (m3 > m2)
{
cout <<"大于" << endl;
}
else
{
cout <<"不大于" << endl;
}
MyString m5 ="ssss";
strcpy(m5.c_str(),"hhh");
cout << m5 <<endl;
MyString m6(128);
cout << "请输入字符串";
cin >> m6;
cout << m6 <<endl;
system("pause");
}
五、 继承和派生
1. 概念
面向对象程序设计有4个主要特点,抽象,封装,继承和多态性。
类与类之间的关系。has-A,uses-A和is-A。has-A,包含关系,用以描述一个类由多个部件类构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。uses-A,一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友元或对象参数传递实现。is-A,继承,关系具有传递性,不具有对称性。
继承是类之间定义的一种重要关系。一个B类继承A类,或称从类A派生类B。类A称为基类(父类),类B称为派生类(子类)。
派生类的定义
class 派生类名:基类名表
{
数据成员和成员函数声明
};
class Parent
{
public:
void print()
{
cout <<"a" << a << endl;
}
protected:
int a;
int b;
};
class Child :public Parent
{
public:
Child()
{
}
Child(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
}
private:
int c;
};
void main()
{
Child c1(1,3,4);
c1.print();
system("pause");
}
2. 继承特点
子类拥有父类的所有非私有成员变量和成员函数。子类就是一种特殊的父类。子类对象可以当做父类对象使用。子类可以拥有父类没有的方法和属性。
3. 派生类的访问控制
派生类继承了基类的全部成员变量和成员方法(除了构造和析构),但是这些成员的访问属性,在派生过程中是可以调整的。
4. 不同的继承方式会改变继承成员的访问属性
c++中的继承方式会影响子类的对外访问属性
public继承,父类成员在子类中保持原有访问级别;
private继承,父类成员在子类中变为private成员;
protected继承,父类中public和protected变为protected,父类中private成员仍为private。
private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。
5. 继承中的构造和析构
类型兼容性原则,是指在需要基类对象的任何地方,都可以使用共有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,共有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,共有派生类都可以解决。类型兼容规则中所指的替代包括以下情况。子类对象可以当做父类对象使用,子类对象可以直接赋值给父类对象,子类对象可以直接初始化父类对象,父类指针可以直接指向子类对象,父类引用可以直接引用子类对象。在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。类型兼容规则是多台性的重要基础之一。
void main()
{
Parent *p1 = NULL;
Child c1;
//父类对象指针指向子类对象引用
p1 = &c1;
p1->print2();
//父类对象初始化子类对象
Parent p2 = c1;
p2.print2();
system("pause");
}
6. 继承中的对象模型
类在c++编译器的内部可以理解为结构体。子类是由父类成员叠加子类新成员得到的。
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化。在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理。
子类对象在创建时会首先调用父类的构造函数。父类构造函数执行结束后,执行子类的构造函数。当父类的构造函数有参数时,需要在子类的初始化列表中显示调用,析构函数调用的先后顺序与构造函数相反。
继承和组合混搭情况下,构造和析构调用原则。先构造父类,再构造成员变量,最后构造自己;先析构自己,再析构成员变量,最后析构父类。
7. 继承中的同名成员变量和同名成员函数
当子类成员变量与父类成员同名时,子类依然从父类继承同名成员,在子类通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符),同行成员存储在内存中的不同位置。
class Base
{
public:
int a;
int b;
public:
void getB()
{
cout <<"b" << b << endl;
}
void print()
{
cout <<"父类print" << endl;
}
};
class Derive:public Base
{
public:
int b;
int c;
public:
Derive(int b, int c)
{
this->b = b;
this->c = c;
}
void getB2()
{
cout <<"b" << b << endl;
}
void print()
{
cout <<"子类print" << endl;
}
};
void main()
{
Derive d1(2, 3);
d1.Base::b = 100;
d1.getB();
d1.getB2();
d1.print();
d1.Base::print();
system("pause");
}
8. 继承的static关键字
继承和static在一起使用时,基类定义的静态成员,将被所有派生类共享;根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质;派生类中访问静态成员,用以下形式显式说明,类名::成员,或通过对象访问,对象名.成员。
9. 多继承
一个类有多个直接基类的继承关系称为多继承。
多继承声明语法
class 派生类名:访问控制 基类名1,访问控制 基类名2,…,访问控制 基类名n
{
};
多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。执行顺序与单继承构造函数情况类似,多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
class B1
{
public:
int a;
};
class B2
{
public:
int b;
};
class D1 : public B1,public B2
{
public:
int c;
public:
void print()
{
cout <<"a" << a << endl;
cout <<"b" << b << endl;
cout <<"c" << c << endl;
}
};
void main()
{
D1 d1;
d1.a = 0;
d1.print();
system("pause");
}
10. 虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。虚继承声明使用关键字virtual。
class O1
{
public:
int o;
};
class B1:virtual public O1
{
public:
int a;
};
class B2: virtual public O1
{
public:
int b;
};
class D1 : public B1,public B2
{
public:
int c;
public:
void print()
{
cout <<"a" << a << endl;
cout <<"b" << b << endl;
cout <<"c" << c << endl;
}
};
void main()
{
D1 d1;
d1.a = 0;
d1.print();
d1.o;
system("pause");
}
11. 多态
面向对象的新需求,根据实际的对象类型判断重写函数的调用。如果父类指针指向的是父类对象则调用父类中定义的函数;如果父类指针指向的是子类对象则调用子类中定义的重写函数。面向对象中的多态,根据实际的对象类型决定函数调用语句的具体调用目标。多态,同样的调用语句有多种不同的表现形式。c++中通过virtual关键字对多态进行支持。使用virtual声明的函数被重写后即可展现出多态特性。
多态案例
class Animal
{
public:
virtual int attack()
{
return 10;//基础攻击10
}
};
class Walf:public Animal
{
public:
virtual int attack()
{
return 20;//狼的攻击为20
}
};
class Cat :public Animal
{
public:
virtual int attack()
{
return 15;//猫的攻击为15
}
};
class Hero
{
public:
Hero(){}
int attack()
{
return 16;
}
};
//决斗函数
void battle(Hero *h,Animal *a)
{
if (h->attack() >a->attack())
{
cout <<"hero胜" << endl;
}
else
{
cout <<"hero败" << endl;
}
}
void main()
{
Hero h1;
Walf w1;
battle(&h1, &w1);
system("pause");
}
多态的3个条件,有继承关系;有虚函数重写;父类指针或父类引用指向子类对象。
多态是设计模式的基础,多态是框架的基础。
12. 多态的理论基础
静态联编和动态联编。联编是指一个程序模块,代码之间互相关联的过程。静态联编(static binding)是程序的匹配、连接在编译阶段实现,也称为早起匹配。重载函数使用静态联编。动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。switch和if语句是动态联编的例子。
c++与c相同,是静态编译型语言。在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象。从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。
c++多态实现,virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用。
c++中多态的实现原理,当类中声明虚函数时,编译器会在类中生成一个虚函数表。虚函数表是由编译器自动生成与维护的。virtual成员函数会被编译器放入虚函数表中。存在虚函数时,每个对象中都有一个指向虚函数表的指针vptr指针。
13. 虚析构函数
虚析构函数用于指引delete正常析构动态对象。
class A
{
public:
A()
{
str = newchar[20];
strcpy(str,"Astr");
cout <<"Astr.." << endl;
}
virtual ~A()
{
delete[]str;
cout <<"Astr~~" << endl;
}
private:
char *str;
};
class B:public A
{
public:
B()
{
str = newchar[20];
strcpy(str,"Bstr");
cout <<"Bstr.." << endl;
}
~B()
{
delete[]str;
cout <<"Bstr~~" << endl;
}
private:
char *str;
};
class C:public B
{
public:
C()
{
str = newchar[20];
strcpy(str,"Cstr");
cout <<"Cstr.." << endl;
}
~C()
{
delete[]str;
cout <<"Cstr~~" << endl;
}
private:
char *str;
};
//只执行了根父类的析构函数,存在内存泄漏问题
//想通过父类的对象指针,把子类的析构函数都执行一遍,需要在根类中使用虚析构函数virtual ~A(){}
void showDelete(A *a)
{
delete a;
}
void main()
{
C *c = new C;
showDelete(c);
system("pause");
}
14. 重载重写重定义
函数重载,必须在同一个类中进行。子类无法重载父类的函数,父类同名函数将被名称覆盖。重载是在编译期间根据参数类型和个数决定函数调用。
函数重写,必须发生于父类与子类之间。并且父类与子类中的函数必须有完全相同的原型。使用virtual声明之后能够产生多态,如果不使用virtual,那是重定义。多态是在运行期间根据具体对象的类型决定函数调用。
15. 证明vptr指针的存在
class Parent1
{
public:
Parent1(int a = 0)
{
this->a = a;
}
public:
virtual void print()
{
cout <<"父类" << endl;
}
private:
int a;
};
class Parent2
{
public:
Parent2(int a = 0)
{
this->a = a;
}
public:
void print()
{
cout <<"父类" << endl;
}
private:
int a;
};
void main()
{
cout <<"sizeof(parent1)" << sizeof(Parent1) << endl;
cout <<"sizeof(parent2)" << sizeof(Parent2) << endl;
system("pause");
}
16. 构造函数中调用虚函数能否实现多态的问题
对象中的vptr指针在对象创建时进行初始化。只有当对象的构造完全结束后vptr的指向才最终确定。父类对象的vptr指向父类虚函数表。子类对象的vptr指向子类虚函数表。
不能。
class Parent1
{
public:
Parent1(int a = 0)
{
this->a = a;
print();
}
public:
virtual void print()
{
cout <<"父类" << endl;
}
private:
int a;
};
class Child1 :public Parent1
{
public:
Child1(int a, int b):Parent1(a)
{
this->b = b;
}
virtual void print()
{
cout <<"子类" << endl;
}
private:
int b;
};
void main()
{
Child1 c1(1,2);
system("pause");
}
要初始化c1.vptr指针,初始化是分步。当执行父类的构造函数时,c1.vptr指向父类的虚函数表。当父类的构造函数运行完毕后,会把c1.vptr指向子类的虚函数表。子类的c1.vptr指针分步完成。
六、 纯虚函数和抽象类
1. 概念
纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数为各派生类提供一个公共界面,接口的封装和设计、软件的模块功能划分。纯虚函数说明形式,
virtual 类型 函数名(参数表) = 0;
一个具有纯虚函数的基类成为抽象类。
c++中没有java中的接口概念,抽象类可以模拟java中的接口类。
在工程中,多继承是被实际开发经验抛弃使用的,因为多重继承带来的代码复杂性远多于其带来的便利,对代码维护性上的影响是灾难性的,而且在设计上,任何多继承都可以使用单继承代替。
class Interface1
{
public:
virtual int add(int a, intb) = 0;
virtual void print() = 0;
};
class Interface2
{
public:
virtual int mult(int a,int b) = 0;
virtual void print() = 0;
};
class Parent
{
public:
int getA()
{
return a;
}
private:
int a;
};
class Child : public Parent, public Interface1, public Interface2
{
public:
virtual int add(int a, intb)
{
return a + b;
}
virtual int mult(int a,int b)
{
return a * b;
}
virtual void print()
{
cout <<"Child:print()执行" << endl;
}
};
void main()
{
Child c1;
c1.print();
Interface1 *i1 = &c1;
cout <<i1->add(1, 3) << endl;
i1->print();
Interface2 *i2 = &c1;
cout <<i2->mult(2, 3) << endl;
i2->print();
system("pause");
}
2. 抽象类思想
虚函数和多态性使成员函数根据调用对象的类型产生不同的动作。多态性特别适合实现分层结构的软件系统,便于对问题抽象时定义共性,实现时定义区别。面向抽象编程是项目开发中重要技能之一。
3. socket库c++模型设计和实现
企业信息系统框架集成第三方产品。一般的企业信息系统都有成熟的框架。软件框架一般不发生变化,能自由的集成第三方厂商的产品。
在企业信息系统框架中集成第三方厂商的socket通信产品和第三方厂商加密产品。第三方厂商的socket通信产品,完成两点之间的通信。第三方厂商的加密产品,完成数据发送时加密,数据接收时解密。
需要支持多个厂商的socket通信产品入围,支持多个厂商的加密产品入围,企业信息系统框架不轻易发生改变。
涉及到的类,通信协议接口,通信协议实现类,加密协议类,加密协议实现类,框架测试类。
CSocketProtocol.h
#pragma once
#include "iostream"
using namespace std;
class CSocketProtocol
{
public:
CSocketProtocol()
{
}
virtual ~CSocketProtocol()
{
}
public:
//客户端初始化,获取handle上下
virtual intcltSocketInit() = 0;
//客户端发报文
virtual intcltSocketSend(unsigned char *buf, int buflen) = 0;
//客户端收报文
virtual intcltSocketRev(unsigned char *buf, int *buflen) = 0;
//客户端释放资源
virtual intcltSocketDestory() = 0;
};
CSocketFactoryImpl1.h
#pragma once
#include "iostream"
using namespace std;
#include "CSocketProtocol.h"
class CSocketFactoryImpl1:public CSocketProtocol
{
public:
//客户端初始化,获取handle上下
virtual intcltSocketInit();
//客户端发报文
virtual int cltSocketSend(unsignedchar *buf, int buflen);
//客户端收报文
virtual intcltSocketRev(unsigned char *buf, int *buflen);
//客户端释放资源
virtual intcltSocketDestory();
private:
unsigned char *str;
int len;
};
CSocketFactoryImpl2.h
#pragma once
#include "iostream"
using namespace std;
#include "CSocketProtocol.h"
class CSocketFactoryImpl2 :public CSocketProtocol
{
public:
//客户端初始化,获取handle上下
virtual intcltSocketInit();
//客户端发报文
virtual intcltSocketSend(unsigned char *buf, int buflen);
//客户端收报文
virtual int cltSocketRev(unsignedchar *buf, int *buflen);
//客户端释放资源
virtual intcltSocketDestory();
private:
unsigned char *str;
int len;
};
CSocketFactoryImpl1.cpp
#include "CSocketFactoryImpl1.h"
//客户端初始化,获取handle上下
int CSocketFactoryImpl1::cltSocketInit()
{
str = NULL;
len = 0;
return 0;
}
//客户端发报文
int CSocketFactoryImpl1::cltSocketSend(unsigned char *buf, int buflen)
{
str = (unsigned char*)malloc(sizeof(unsigned char)* buflen);
if (str == NULL)
{
return -1;
}
memcpy(str, buf, buflen);
len = buflen;
return 0;
}
//客户端收报文
int CSocketFactoryImpl1::cltSocketRev(unsigned char *buf, int *buflen)
{
if (buf == NULL || buflen== NULL)
{
return -1;
}
*buflen = this->len;
memcpy(buf, str, *buflen);
return 0;
}
//客户端释放资源
int CSocketFactoryImpl1::cltSocketDestory()
{
if (str != NULL)
{
free(str);
str = NULL;
len = 0;
}
return 0;
}
CSocketFactoryImpl2.cpp
#include "CSocketFactoryImpl2.h"
//客户端初始化,获取handle上下
int CSocketFactoryImpl2::cltSocketInit()
{
str = NULL;
len = 0;
return 0;
}
//客户端发报文
int CSocketFactoryImpl2::cltSocketSend(unsigned char *buf, int buflen)
{
str = (unsigned char*)malloc(sizeof(unsigned char)* buflen);
if (str == NULL)
{
return -1;
}
memcpy(str, buf, buflen);
len = buflen;
return 0;
}
//客户端收报文
int CSocketFactoryImpl2::cltSocketRev(unsigned char *buf, int *buflen)
{
if (buf == NULL || buflen== NULL)
{
return -1;
}
*buflen = this->len;
memcpy(buf, str, *buflen);
return 0;
}
//客户端释放资源
int CSocketFactoryImpl2::cltSocketDestory()
{
if (str != NULL)
{
free(str);
str = NULL;
len = 0;
}
return 0;
}
CEncDecProtocol.h
#pragma once
#include "iostream"
using namespace std;
class CEncDecProtocol
{
public:
CEncDecProtocol()
{
}
virtual ~CEncDecProtocol()
{
}
public:
virtual intEncData(unsigned char *plain, int plainlen, unsigned char *cryptdata, int*cryptlen) = 0;
virtual intDecData(unsigned char *cryptdata, int cryptlen, unsigned char *plain, int*plainlen) = 0;
};
ZxEncDec.h
#pragma once
#include "CEncDecProtocol.h"
using namespace std;
class ZxEncDec:public CEncDecProtocol
{
public:
virtual intEncData(unsigned char *plain, int plainlen, unsigned char *cryptdata, int*cryptlen);
virtual intDecData(unsigned char *cryptdata, int cryptlen, unsigned char *plain, int*plainlen);
};
ZxEncDec.cpp
#include "ZxEncDec.h"
#include "des.h"
int ZxEncDec::EncData(unsigned char *plain, int plainlen, unsigned char*cryptdata, int *cryptlen)
{
int ret = 0;
ret = DesEnc(plain,plainlen, cryptdata, cryptlen);
if (ret != 0)
{
cout <<"func EncDec():err" << ret << endl;
return ret;
}
return ret;
}
int ZxEncDec::DecData(unsigned char *cryptdata, int cryptlen, unsignedchar *plain, int *plainlen)
{
int ret = 0;
ret = DesDec(cryptdata,cryptlen, plain, plainlen);
if (ret != 0)
{
cout <<"func DesDec():err" << ret << endl;
return ret;
}
return ret;
}
使用c语言函数实现框架
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "CSocketFactoryImpl1.h"
#include "CSocketFactoryImpl2.h"
#include "CEncDecProtocol.h"
#include "ZxEncDec.h"
//发送和接收框架
int SckSendAndRec(CSocketProtocol *csp, unsigned char *in, int inlen,unsigned char *out, int *outlen)
{
int ret = 0;
ret =csp->cltSocketInit();
if (ret != 0)
{
goto End;
}
ret =csp->cltSocketSend(in,inlen);
if (ret != 0)
{
goto End;
}
ret =csp->cltSocketRev(out, outlen);
if (ret != 0)
{
goto End;
}
End:
ret =csp->cltSocketDestory();
return ret;
}
//发送和接收加密框架
int SckSendAndRec_EncDec(CSocketProtocol *csp,CEncDecProtocol *cedp,unsigned char *in, int inlen, unsigned char *out, int *outlen)
{
int ret = 0;
unsigned char data[4096];
int datalen = 0;
ret =csp->cltSocketInit();
if (ret != 0)
{
goto End;
}
ret = cedp->EncData(in,inlen, data, &datalen);
if (ret != 0)
{
goto End;
}
ret =csp->cltSocketSend(data, datalen);//发送的是密文
if (ret != 0)
{
goto End;
}
ret =csp->cltSocketRev(data, &datalen);//收到的也是密文
if (ret != 0)
{
goto End;
}
//对收到的数据解密
ret =cedp->DecData(data, datalen, out, outlen);
if (ret != 0)
{
goto End;
}
End:
ret =csp->cltSocketDestory();
return ret;
}
int main2()
{
int ret = 0;
unsigned char in[4096];
int inlen;
unsigned char out[4096];
int outlen = 0;
strcpy((char *)in,"abcdefghi");
inlen = 9;
strcpy((char *)out,"abc");
CSocketProtocol *csp =NULL;
CEncDecProtocol *cedp =NULL;
cedp = new ZxEncDec;
CSocketFactoryImpl1 sfi1;
csp = &sfi1;
csp = newCSocketFactoryImpl2;
ret = SckSendAndRec(csp,in, inlen, out, &outlen);
if (ret != 0)
{
cout <<"func SckSendAndRec:err" << ret << endl;
return ret;
}
ret =SckSendAndRec_EncDec(csp, cedp, in, inlen, out, &outlen);
out[outlen + 1] = 0;
printf("%s",out);
delete csp;
system("pause");
return ret;
}
int main1()
{
int ret = 0;
unsigned char in[4096];
int inlen;
unsigned char out[4096];
int outlen = 0;
strcpy((char *)in,"abcdefghi");
inlen = 9;
strcpy((char *)out,"abc");
CSocketProtocol *csp =NULL;
CSocketFactoryImpl1 sfi1;
csp = &sfi1;
csp = newCSocketFactoryImpl2;
ret = SckSendAndRec(csp,in, inlen, out, &outlen);
if (ret != 0)
{
cout <<"func SckSendAndRec:err" << ret << endl;
return ret;
}
delete csp;
return ret;
system("pause");
}
使用c++类实现框架
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "CSocketFactoryImpl1.h"
#include "CSocketFactoryImpl2.h"
#include "CEncDecProtocol.h"
#include "ZxEncDec.h"
/*
//抽象类在多继承中的应用
class MainOp :public CSocketProtocol, public CEncDecProtocol
{
};
*/
class MainOp
{
public:
MainOp()
{
this->csp =NULL;
this->csp =NULL;
}
MainOp(CSocketProtocol*csp, CEncDecProtocol *cedp)
{
this->csp =csp;
this->cedp =cedp;
}
voidsetCsp(CSocketProtocol *csp)
{
this->csp =csp;
}
voidsetCedp(CEncDecProtocol *cedp)
{
this->cedp =cedp;
}
public:
//发送和接收框架
intSckSendAndRec(CSocketProtocol *csp, unsigned char *in, int inlen, unsigned char*out, int *outlen)
{
int ret = 0;
ret =csp->cltSocketInit();
if (ret != 0)
{
gotoEnd;
}
ret =csp->cltSocketSend(in, inlen);
if (ret != 0)
{
gotoEnd;
}
ret =csp->cltSocketRev(out, outlen);
if (ret != 0)
{
gotoEnd;
}
End:
ret =csp->cltSocketDestory();
return ret;
}
//发送和接收加密框架
int SckSendAndRec_EncDec(CSocketProtocol *csp, CEncDecProtocol *cedp, unsigned char *in, int inlen,unsigned char *out, int *outlen)
{
int ret = 0;
unsigned chardata[4096];
int datalen = 0;
ret =csp->cltSocketInit();
if (ret != 0)
{
gotoEnd;
}
ret =cedp->EncData(in, inlen, data, &datalen);
if (ret != 0)
{
goto End;
}
ret =csp->cltSocketSend(data, datalen);//发送的是密文
if (ret != 0)
{
gotoEnd;
}
ret =csp->cltSocketRev(data, &datalen);//收到的也是密文
if (ret != 0)
{
gotoEnd;
}
//对收到的数据解密
ret =cedp->DecData(data, datalen, out, outlen);
if (ret != 0)
{
gotoEnd;
}
End:
ret =csp->cltSocketDestory();
return ret;
}
intSckSendAndRec_EncDec(unsigned char *in, int inlen, unsigned char *out, int*outlen)
{
int ret = 0;
unsigned chardata[4096];
int datalen = 0;
ret =this->csp->cltSocketInit();
if (ret != 0)
{
gotoEnd;
}
ret =this->cedp->EncData(in, inlen, data, &datalen);
if (ret != 0)
{
gotoEnd;
}
ret =csp->cltSocketSend(data, datalen);//发送的是密文
if (ret != 0)
{
gotoEnd;
}
ret =csp->cltSocketRev(data, &datalen);//收到的也是密文
if (ret != 0)
{
gotoEnd;
}
//对收到的数据解密
ret =cedp->DecData(data, datalen, out, outlen);
if (ret != 0)
{
gotoEnd;
}
End:
ret =csp->cltSocketDestory();
return ret;
}
private:
CSocketProtocol *csp;
CEncDecProtocol *cedp;
};
int main()
{
int ret = 0;
unsigned char in[4096];
int inlen;
unsigned char out[4096];
int outlen = 0;
strcpy((char *)in,"abcdefghi");
inlen = 9;
strcpy((char *)out,"abc");
CSocketProtocol *csp =NULL;
CEncDecProtocol *cedp =NULL;
cedp = new ZxEncDec;
CSocketFactoryImpl1 sfi1;
csp = &sfi1;
csp = newCSocketFactoryImpl2;
MainOp *op = newMainOp(csp, cedp);
ret =op->SckSendAndRec_EncDec(in, inlen, out, &outlen);
if (ret != 0)
{
cout <<"func SckSendAndRec:err" << ret << endl;
return ret;
}
out[outlen + 1] = 0;
printf("%s",out);
delete csp;
delete cedp;
delete op;
system("pause");
return ret;
}
七、 c动态库升级成框架案例
动态库是抽象类的一套接口,单独封装成模块,供别人调用,无法扩展。框架,能自由的扩展的程序。
一般的企业信息系统都有成熟的框架,可以是c语言,也可以是c++。软件框架一般不发生变化,能自由的集成第三方厂商的产品。
在socket通信库中,完成数据加密功能,有n个厂商的加密产品可选择,实现动态库和第三方产品的解耦合。
在动态库内部,提前定义好一套接口(函数指针类型的定义)。 在动态库外部,发布一套接口协议(发布.h)。厂商根据发布的.h实现函数原型(编写子任务)。把厂商的入口地址注入到框架中。在框架中利用回调函数调用第三方的任务。
八、 函数模版和类模板
1. 概念和语法
c++提供了函数模版(function template)。函数模板,实际上是建立一个通用函数,其函数类型和形参类型具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个函数模板来代替。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
c++提供了两种模板机制,函数模板、类模板。
类属,类型参数化,又称参数模板。使得程序可以从逻辑功能上抽象,把被处理的对象类型。作为参数传递。
模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
函数模板定义形式
template <类型形式参数表>
类型形式参数的形式为:typename T1, typename T2,…,typename Tn或class T1,class T2,…,class Tn。
函数模板声明
template <类型形式参数表>
类型 函数名(形式参数表)
{
函数体
}
函数模板定义由模板说明和函数定义组成。模板说明的类属参数必须在函数定义中至少出现一次。函数参数表中可以使用类属类型参数,也可以使用一般类型参数。
数组排序的例子
//创建函数模板
template<typename T, typename T2>
int mySort(T *array, T2 size)
{
T tmp;
if (array == NULL)
{
return -1;
}
for (int i = 0; i <size - 1; i++)
{
for (int j = 0;j < size - 1 - i; j++)
{
if(array[j] > array[j + 1])
{
tmp= array[j];
array[j]= array[j + 1];
array[j+ 1] = tmp;
}
}
}
return 0;
}
template<typename T, typename T2>
int myPrint(T *array, T2 size)
{
for (int i = 0; i <size; i++)
{
cout <<array[i] << "\t";
}
cout << endl;
return 0;
}
void main()
{
//int类型
{
int array[] = {11, 23, 2, 34, 54, 21, 3, 345, 7 };
int size =sizeof(array) / sizeof(*array);
myPrint<int,int>(array, size);
cout <<"排序后" << endl;
mySort<int,int>(array, size);
myPrint<int,int>(array, size);
}
//char类型
{
char buf[]="basdfwefradfa";
int size2 = sizeof(buf) /sizeof(*buf);
myPrint<char,char>(buf, size2);
cout << "排序后\n" << endl;
mySort<char,char>(buf, size2);
myPrint<char,char>(buf, size2);
}
system("pause");
}
2. 函数模板和函数重载
函数模板的本事是类型参数化,将严格的按照类型匹配,不会进行自动类型转换。普通函数的调用可以进行隐式的类型转换。
函数模板可以像普通函数一样被重载;c++编译器有限考虑普通函数;如果函数模板可以产生一个更好的匹配,那么选择匹配;可以通过空模板实参列表的语法限定编译器只通过模板匹配。
当函数模板和普通函数都符合调用时,优先调用普通函数。若显示调用函数模板,则使用<>类型列表。如果函数模板产生更好的匹配,使用函数模板。调用普通函数,可以隐式类型转换。
3. c++编译器函数模板机制
编译器编译原理,gcc,gnu c compliler编译器的做事是stallman,也是gnu项目的奠基者。gcc最初是作为c语言的编译器,现在也支持多种语言了,如c、c++、java、pascal、ada、cobol语言等。gcc支持多种硬件平台。
gcc的主要特征,gcc是一个可移植的编译器;能跨平台交叉编译;gcc有多种语言前端,用于解析不同的语言;gcc是按模块化设计的,可以加入新语言和新cpu架构的支持;gcc是自由软件。
gcc编译过程,预处理(pre-processing);编译(compiling);汇编(assembling);链接(linking)。
gcc常用编译选项
-o,产生目标(.i、.s、.o等);
-c,通知gcc取消链接步骤,即编译源码并在最后生成目标文件;
-E,只运行c预编译器;
-S,告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s;
-Wall,使gcc对源文件的代码有问题的地方发出警告;
-Idir,将dir目录加入搜索文件的目录路径;
-Ldir,将dir目录加入搜索库的目录路径;
-Ilib,链接lib库;
-g,在目标文件中嵌入调试信息,以便gdb之类的调试程序调试。
例子:
gcc -E hello.c -o hello.i 预编译
gcc -S hello.i -o hello.s 编译
gcc -c hello.s -o hello.o 汇编
gcc hello.o -o hloo 链接
函数模板机制结论,编译器并不是把函数模板能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
4. 类模板
类模板与函数模板的定义和使用类似。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。
类模板 由模板说明和类说明构成。
template <类型形式参数>
类声明
例如:
template <typename Type>
class TClass
{
private:
Type DateMember;
}
类模板用于实现类所需数据的类型参数化。类模板在表示如数组、表、图等数据结构显得特别重要;这些数据结构的表示和算法不受所包含的元素类型的影响。
单个类模板语法
//创建模板类
template<typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
void printT()
{
cout <<"t:" << t << endl;
}
public:
T t;
};
void main()
{
A<int>a1(10),a2(20),a3(30);
a1.printT();
a2.printT();
a3.printT();
system("pause");
}
继承类模板。子模板类派生时,需要具体化模板类,c++编译器需要知道父类的数据类型。要知道父类所占内存大小是多少,只有数据类型固定下来,才知道如何分配内存。
//创建模板类
template<typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
void printT()
{
cout <<"t:" << t << endl;
}
public:
T t;
};
class B :public A<int>
{
public:
B(int a = 15,int b = 20):A<int>(a)
{
this->b = b;
}
private:
int b;
};
void main()
{
B b(1, 3);
b.printT();
system("pause");
}
从模板类派生模板类
//创建模板类
template<typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
void printT()
{
cout <<"t:" << t << endl;
}
public:
T t;
};
class B :public A<int>
{
public:
B(int a = 15,int b = 20):A<int>(a)
{
this->b = b;
}
private:
int b;
};
template <typename T>
class C :public A<T>
{
public:
C(T c,T a) :A<T>(a)
{
this->c = c;
}
void printC()
{
cout <<"c:" << c << endl;
}
private:
int c;
};
void main()
{
C<int> c(2, 5);
c.printC();
system("pause");
}
5. 类模板语法
所有的类模板函数写在类的内部
template <typename T>
class Complex
{
friend ostream & operator<<(ostream &out, Complex &c3)
{
out << "a"<< c3.a << "--b" << c3.b;
return out;
}
public:
Complex(T a, T b){
this->a = a;
this->b = b;
}
void print()
{
cout <<"a:" << a << endl;
cout <<"b:" << b << endl;
}
public:
//+重载
Complex operator+(Complex&c2)
{
Complex tmp(a +c2.a, b + c2.b);
return tmp;
}
private:
T a;
T b;
};
/*
ostream & operator<<(ostream &out, Complex<int>&c3)
{
out << "a" << c3.a << "--b"<< c3.b;
return out;
}
*/
void main()
{
Complex<int> c1(1,3);
Complex<int> c2(2,6);
Complex<int> c3 = c1+ c2;
cout << c3 <<endl;
system("pause");
}
所有函数都写在类的外部
template <typename T>
class Complex
{
friend ostream &operator<<<T>(ostream &out, Complex &c3);
public:
Complex(T a, T b);
void print();
public:
//+重载
Complex operator+(Complex&c2);
private:
T a;
T b;
};
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::print()
{
cout <<"a:" << a << endl;
cout <<"b:" << b << endl;
}
template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c2)
{
Complex tmp(a + c2.a, b +c2.b);
return tmp;
}
template <typename T>
ostream & operator<<(ostream &out, Complex<T>&c3)
{
out << "a"<< c3.a << "--b" << c3.b;
return out;
}
void main()
{
Complex<int> c1(1,3);
Complex<int> c2(2,6);
Complex<int> c3 = c1+ c2;
cout << c3 <<endl;
system("pause");
}
所有函数都写在类的外部,h和cpp分开
编写complex.h文件
#include "iostream"
using namespace std;
template <typename T>
class Complex
{
friend ostream &operator<<<T>(ostream &out, Complex &c3);
public:
Complex(T a, T b);
void print();
public:
//+重载
Complex operator+(Complex&c2);
private:
T a;
T b;
};
编写cpp文件,一般令起名为hpp文件,complex.hpp
#include "complex.h"
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::print()
{
cout <<"a:" << a << endl;
cout << "b:"<< b << endl;
}
template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c2)
{
Complex tmp(a + c2.a, b +c2.b);
return tmp;
}
template <typename T>
ostream & operator<<(ostream &out, Complex<T>&c3)
{
out << "a"<< c3.a << "--b" << c3.b;
return out;
}
测试文件,注意要包含.hpp文件而不是.h文件。
#include "iostream"
using namespace std;
#include "complex.hpp"
void main()
{
Complex<int> c1(1,3);
Complex<int> c2(2,6);
Complex<int> c3 = c1+ c2;
c3.print();
cout << c3 <<endl;
system("pause");
}
6. 类模板中的static关键字
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员。和非模板类的static数据成员一样,模板类的static数成员也应该在文件范围定义和初始化。每个模板类有自己的类模板的static数据成员副本。
#include "iostream"
using namespace std;
template <typename T>
class A
{
public:
static T m_a;
};
template <typename T>
T A<T> :: m_a = 10;
void main()
{
A<int> a1, a2, a3;
cout << a1.m_a<< a2.m_a << a3.m_a << endl;
a1.m_a = 12;
a2.m_a = 22;
a3.m_a = 32;
cout << a1.m_a<< a2.m_a << a3.m_a << endl;
A<char> b1, b2, b3;
b1.m_a = 'a';
b2.m_a++;
b3.m_a++;
cout << b1.m_a<< b2.m_a << b3.m_a << endl;
system("pause");
}
7. 类模板在项目开发中的应用
模板是c++类型参数化的多态工具,c++提供函数模板和类模板。模板定义以模板说明开始,类属参数必须在模板定义中至少出现一次。同一个类属参数可以用于多个模板。类属参数可用于函数的参数类型、返回类型和声明函数中的变量。模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。模板称为模板函数,实例化的类模板称为模板类。函数模板可以用多种方式重载。类模板可以在类层次中使用。
编写一个vector集合类模板
创建MyVector.h文件
#include "iostream"
using namespace std;
template <typename T>
class MyVector
{
friend ostream &operator<<<T>(ostream &out, const MyVector &obj);
//构造函数和析构函数
public:
MyVector(int size);
MyVector(const MyVector&obj);
~MyVector();
//封装函数
public:
int getLen()
{
return m_len;
}
//重载函数
public:
T& operator[](intindex);
MyVector &operator=(const MyVector &obj);
protected:
T *m_space;
int m_len;
};
编写MyVector.hpp文件
#include "MyVector.h"
template <typename T>
MyVector<T>::MyVector(int size)
{
m_len = size;
m_space = new T[m_len];
}
template <typename T>
MyVector<T>::MyVector(const MyVector &obj)
{
m_len = obj.m_len;
m_space = new T[m_len];
for (int i = 0; i <obj.m_len; i++)
{
m_space[i] =obj.m_space[i];
}
}
template <typename T>
MyVector<T>::~MyVector()
{
if (m_space != NULL)
{
delete[]m_space;
m_space = NULL;
m_len = 0;
}
}
template <typename T>
T& MyVector<T>::operator[](int index)
{
return m_space[index];
}
template <typename T>
MyVector<T> & MyVector<T>::operator=(constMyVector<T> &obj)
{
//先释放掉旧的内存
if (m_space != NULL)
{
delete[]m_space;
m_space = NULL;
m_len = 0;
}
//根据参数对象分配新的内存
m_len = obj.m_len;
m_space = new T[m_len];
//copy数据
for (int i = 0; i <m_len; i++)
{
m_space[i] =obj[i];
}
return *this;
}
template <typename T>
ostream & operator<<(ostream &out, const MyVector<T>&obj)
{
for (int i = 0; i < obj.m_len;i++)
{
out <<obj.m_space[i] << "\t";
}
return out;
}
编写测试文件
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "MyVector.hpp"
class Person
{
public:
Person()
{
age = 0;
strcpy(name,"");
}
Person(char *name, intage)
{
this->age =age;
strcpy(this->name,name);
}
public:
char *getName()
{
return name;
}
int getAge()
{
return age;
}
private:
int age;
char name[32];
};
void main()
{
Person p1("王武", 12);
Person p2("赵思", 22);
Person p3("张三", 32);
cout << p1.getName()<< endl;
MyVector<Person *>v1(3);
v1[0] = &p1;
v1[1] = &p2;
v1[2] = &p3;
for (int i = 0; i <v1.getLen(); i++)
{
Person *tmp =v1[i];
cout <<tmp->getAge() << "--" << tmp->getName() <<endl;
}
system("pause");
}
void main3(){
Person p1("王武",12);
Person p2("赵思", 22);
Person p3("张三", 32);
cout << p1.getName()<< endl;
MyVector<Person>v1(3);
v1[0] = p1;
v1[1] = p2;
v1[2] = p3;
for (int i = 0; i <v1.getLen(); i++)
{
Person tmp =v1[i];
cout <<tmp.getAge() << "--" << tmp.getName() << endl;
}
system("pause");
}
void main2()
{
MyVector<char>v1(10);
v1[0] = 'a';
v1[2] = 'b';
v1[3] = 'c';
v1[4] = 'd';
cout << v1 <<endl;
system("pause");
}
void main1()
{
MyVector<int>v1(10);
for (int i = 0; i <v1.getLen(); i++)
{
v1[i] = i;
}
for (int i = 0; i <v1.getLen(); i++)
{
cout <<v1[i] << "\t";
}
cout << endl;
//*
MyVector<int> v2 =v1;
for (int i = 0; i <v1.getLen(); i++)
{
cout <<v2[i] << "\t";
}
//*/
cout << endl;
//cout << v2;
system("pause");
}
九、 c++的类型转换
c风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是
Type b = (Type)a;
c++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。
static_cast 静态类型转换。如int转换成char。
reinterpreter_cast 重新解释类型。
dynamic_cast 命名上理解是动态类型转换,如子类和父类之间的多态类型转换。
const_cast 字面里理解就是去const属性。
格式例子:
#include "iostream"
using namespace std;
void printbuf(const char *p)
{
//p[0] = 'a';
char *p1 = NULL;
p1 = const_cast<char*>(p);
p1[0] = 'z';
cout << p <<endl;
}
void main()
{
char buf[] ="asdfsdljvsfsdsdf";
printbuf(buf);
system("pause");
}
class Person
{
public:
virtual void eat() = 0;
};
class Student:public Person
{
public:
virtual void eat()
{
cout <<"在学校食堂吃饭" << endl;
}
void play()
{
cout <<"学生打游戏" << endl;
}
};
class Teacher:public Person
{
public:
virtual void eat()
{
cout <<"在教室食堂吃饭" << endl;
}
void teach()
{
cout <<"教室教课" << endl;
}
};
class Animal
{
};
void playObj(Person *obj)
{
obj->eat();
//dynamic_cast做运行时类型识别
Student *str =dynamic_cast<Student *>(obj);
if (str != NULL)
{
str->play();
}
Teacher *tch =dynamic_cast<Teacher *>(obj);
if (tch != NULL)
{
tch->teach();
}
}
void main2()
{
Student s1;
Teacher t1;
playObj(&s1);
playObj(&t1);
Person *p1 = NULL;
//p1 = &s1;
p1 = static_cast<Person*>(&s1);
p1 =reinterpret_cast<Person *>(&s1);
//s1 =static_cast<Student *>(&p1);
//s1 =reinterpret_cast<Student>(*p1);
Animal a1;
//p1 =static_cast<Person *>(&a1);
p1 =reinterpret_cast<Person *>(&a1);
system("pause");
}
void main1()
{
double dpi = 3.1415926;
//C语言风格的类型转换
int ipi1 = (int)dpi;
cout << ipi1<< endl;
//c++的类型转换
int ipi2 =static_cast<int>(dpi);
int ipi3 = dpi;//c语言中的隐式类型转换均可使用static_cast<>
cout << ipi2<< endl;
//char * = int *
char *str = "hellojava";
int *istr1 = NULL;
istr1 = (int *)str;
cout << istr1<< endl;
int *istr2 = NULL;
//istr2 =static_cast<int *>(str);
istr2 =reinterpret_cast<int *>(str);
cout << str <<endl;
cout << istr2<< endl;
system("pause");
}
转换时,要清楚,转的变量类型转换前是什么类型,类型转换后是什么类型,转换后哟什么结果。一般情况下,不建议进行类型转换,避免进行类型转换。
十、 异常处理机制
1. 概念
异常是一种程序控制机制,与函数机制独立和互补。函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常另一种控制结构,依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈。
异常设计目的,栈机制是一种高度节律控制机制,面向对象编程却要求对象之间有方向,有目的控制传动。从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是为了进行错误处理。异常设计出来之后,却发现在错误处理方面获得了最大的好处。
2. 异常处理的实现
基本语法
抛已成的程序段
void Fun()
{
……
throw 表达式;
……
}
捕获并处理异常的程序段
try{
复合语句
}
catch(异常类型声明)
{
复合语句;
}
catch(异常 (形参))
{
复合语句;
}
……
若有异常则通过throw操作创建一个异常对象并抛掷。
将可能抛出异常的程序段嵌在try块之中,并通过正常的顺序执行到达try语句,然后执行try块内的保护段。如果在保护段执行期间没有引起异常,那么跟在try块后的catch字句就不执行。程序从try块后跟随的最后一个catch字句后面的语句继续执行下去。catch字句按其在try块后出现的顺序被检查。匹配的catch自己将捕获并处理异常(或继续抛掷异常)。如果匹配的处理器未找到,则运行函数terminate将自动调用,其缺省功能是调用abort终止程序。处理不了的程序,可以在catch的最后一个分支,使用throw语法,向上扔。异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配。所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题。异常捕捉严格按照类型匹配,异常捕捉的类型匹配严格,不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到。
#include "iostream"
using namespace std;
void divide(int x, int y)
{
if (y == 0)
{
throw x;
}
cout <<"x/y=" << x / y << endl;
}
void mydivide(int x, int y)
{
try
{
divide(x, y);
}
catch (...)
{
cout <<"接收到divide()异常,没有处理向上抛出";
throw;
}
}
void main()
{
mydivide(10, 0);
system("pause");
}
void main1()
{
try
{
divide(2, 5);
divide(10, 0);
}
catch (int e)
{
cout << e<< "被零除" << endl;
}
catch (...)
{
cout <<"其他异常" << endl;
}
system("pause");
}
3. 异常处理的基本思想
c++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
异常是专门针对抽象编程中的一系列错误处理的。c++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃。但是错误处理的特征是遇到错误之后就要转到若干级之上进行重新处理。异常超脱于函数机制,决定了其对函数的跨越式回跳。异常跨越函数。
4. 栈解旋(unwinding)
异常抛出后,从进入try块起,到异常被抛前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋。
class Test2
{
public:
Test2(int a, int b)
{
this->a = a;
this->b = b;
cout <<"构造函数" << endl;
}
~Test2()
{
cout <<"析构函数" << endl;
}
private:
int a;
int b;
};
void test1()
{
Test2 t1(2,5),t2(3,7);
cout << "异常发生..." << endl;
throw 1;
}
void main()
{
try
{
test1();
}
catch (int a)
{
cout <<"int 异常" << endl;
}
catch (...)
{
cout <<"未知异常" << endl;
}
system("pause");
}
5. 异常接口声明
为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如,void func()throw(A,B,C,D);这个函数function()能够且只能抛出类型ABCD及其子类型的异常。如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常。一个不抛出任何类型异常的函数可以声明为void func() throw();。如果一个函数跑出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中止程序。
6. 异常类型和异常变量的声明周期
throw的异常是有类型的,可以是数字、字符串和类对象。throw的异常是有类型的,catch严格按照类型进行匹配。注意,异常对象的内存类型。
#include "iostream"
using namespace std;
void my_strcpy2(char *to, char *from)
{
if (from == NULL)
{
throw "buf源出错";
}
else if (to == NULL)
{
throw "buf目的出错";
}
else if (*from == 'a')
{
throw "copy过程出错";
}
while (*from != '\0')
{
*to = *from;
to++;
from++;
}
*to = '\0';
}
void my_strcpy3(char *to, char *from)
{
if (from == NULL)
{
throw 1;
}
else if (to == NULL)
{
throw 2;
}
else if (*from == 'a')
{
throw 3;
}
while (*from != '\0')
{
*to = *from;
to++;
from++;
}
*to = '\0';
}
class BadSrcType{};
class BadDestType{};
class BadProcessType
{
public:
BadProcessType()
{
cout <<"BadProcessType()构造函数" << endl;
}
BadProcessType(constBadProcessType &obj)
{
cout <<"BadProcessType()拷贝构造函数" << endl;
}
~BadProcessType()
{
cout <<"BadProcessType()析构函数" << endl;
}
};
void my_strcpy4(char *to, char *from)
{
if (from == NULL)
{
throwBadSrcType();
}
else if (to == NULL)
{
throwBadDestType();
}
else if (*from == 'a')
{
cout <<"预备抛出BadProcessType异常" << endl;
throwBadProcessType();
}
while (*from != '\0')
{
*to = *from;
to++;
from++;
}
*to = '\0';
}
void main()
{
int ret = 0;
char buf1[] = "abdcsdfs";
char buf2[1024] = { 0 };
try
{
my_strcpy4(buf2,buf1);
}
catch (int e)
{
cout << e<< "--int类型异常" << endl;
}
catch (char *e)
{
cout << e<< "--char类型异常" << endl;
}
catch (BadSrcType e)
{
cout <<"--BadSrcType类型异常" << endl;
}
catch (BadDestType e)
{
cout <<"--BadDestType类型异常" << endl;
}
//如果使用异常时,接收一个变量,则copy抛出的异常类型变量
/*
catch (BadProcessType e)
{
cout <<"--BadProcessType类型异常" << endl;
}
//*/
//如果使用异常时,接收一个引用,则直接使用抛出的异常类型变量
/*
catch (BadProcessType&e)
{
cout <<"--BadProcessType类型异常引用" << endl;
}
//*/
//如果使用异常时,指针可以和元素的引用同时使用
catch (BadProcessType *e)
{
cout <<"--BadProcessType类型异常引用" << endl;
}
catch (...)
{
cout <<"未知异常" << endl;
}
/*
if (ret != 0)
{
switch (ret)
{
case 1:
cout<< "buf源出错" << endl;
break;
case 2:
cout<< "buf目的出错" << endl;
break;
case 3:
cout<< "copy过程出错" << endl;
break;
default:
cout<< "未知错误" << endl;
break;
}
}
//*/
cout <<"buf2:" << buf2 << endl;
system("pause");
}
int my_strcpy1(char *to, char *from)
{
if (from == NULL)
{
return 1;
}
else if (to == NULL)
{
return 2;
}
else if (*from == 'a')
{
return 3;
}
while (*from != '\0')
{
*to = *from;
to++;
from++;
}
*to = '\0';
return 0;
}
void main2()
{
int ret = 0;
char buf1[] ="babdcsdfs";
char buf2[1024] = { 0 };
ret = my_strcpy1(buf2,buf1);
if (ret != 0)
{
switch (ret)
{
case 1:
cout<< "buf源出错" << endl;
break;
case 2:
cout<< "buf目的出错" << endl;
break;
case 3:
cout<< "copy过程出错" << endl;
break;
default:
cout<< "未知错误" << endl;
break;
}
}
cout <<"buf2:" << buf2 << endl;
system("pause");
}
class Test2
{
public:
Test2(int a, int b)
{
this->a = a;
this->b = b;
cout <<"构造函数" << endl;
}
~Test2()
{
cout <<"析构函数" << endl;
}
private:
int a;
int b;
};
void test1()
{
Test2 t1(2,5),t2(3,7);
cout << "异常发生..." << endl;
throw 1;
}
void main1()
{
try
{
test1();
}
catch (int a)
{
cout <<"int 异常" << endl;
}
catch (...)
{
cout <<"未知异常" << endl;
}
system("pause");
}
7. 异常的层次结构
异常是类,异常派生,异常中的数据,数据成员,按找引用传递异常。
案例,一个数组类,重载[]操作。数据初始化时,对数组的个数进行有效检查。index<0,抛出异常eNegative;index = 0,抛出异常eZero;index < 1000,抛出异常eTooBig;index < 10,抛出异常eTooSmall;eSize类是以上类的父类,实现有参数构造,并定义virtual void printErr()输出错误。
#include "iostream"
using namespace std;
class MyArray
{
public:
MyArray(int len);
~MyArray();
public:
int getLen();
public:
int & operator[](intindex);
private:
int m_len;
int *m_space;
public:
class eSize
{
public:
eSize(int size)
{
m_len =size;
}
virtual voidprintErr()
{
cout<< "size:" << m_len << endl;
}
protected:
int m_len;
};
class eNagetive:publiceSize
{
public:
eNagetive(intsize) :eSize(size)
{
}
virtual voidprintErr()
{
cout<< "eNagetive--size:" << m_len << endl;
}
};
class eZero :public eSize
{
public:
eZero(int size):eSize(size)
{
}
virtual voidprintErr()
{
cout<< "eZero--size:" << m_len << endl;
}
};
class eTooBig :publiceSize
{
public:
eTooBig(intsize) :eSize(size)
{
}
virtual voidprintErr()
{
cout<< "eTooBig--size:" << m_len << endl;
}
};
class eTooSmall :publiceSize
{
public:
eTooSmall(intsize) :eSize(size)
{
}
virtual voidprintErr()
{
cout<< "eTooSmall--size:" << m_len << endl;
}
};
};
MyArray::MyArray(int len)
{
if (len < 0)
{
throweNagetive(len);
}
else if (len == 0)
{
throweZero(len);
}
else if (len > 1000)
{
throweTooBig(len);
}
else if (len < 5)
{
throweTooSmall(len);
}
m_len = len;
m_space = new int[m_len];
}
MyArray::~MyArray()
{
delete[] m_space;
m_space = NULL;
m_len = 0;
}
int MyArray::getLen()
{
return m_len;
}
int & MyArray::operator[](int index)
{
return m_space[index];
}
void main()
{
try
{
MyArray m1(0);
for (int i = 0;i < m1.getLen(); i++)
{
m1[i] =i;
}
for (int i = 0;i < m1.getLen(); i++)
{
cout<< m1[i] << "\t";
}
cout <<endl;
}
catch (MyArray::eSize&e)
{
e.printErr();
}
catch (...)
{
cout <<"未知异常" << endl;
}
system("pause");
}
void main1()
{
try{
MyArray m1(0);
for (int i = 0;i < m1.getLen(); i++)
{
m1[i] =i;
}
for (int i = 0;i < m1.getLen(); i++)
{
cout<< m1[i] << "\t";
}
cout <<endl;
}
catch (MyArray::eNagetivee)
{
cout <<"eNagetive异常" << endl;
}
catch (MyArray::eZero e)
{
cout <<"eZero异常" << endl;
}
catch (MyArray::eTooBig e)
{
cout <<"eTooBig异常" << endl;
}
catch (MyArray::eTooSmalle)
{
cout <<"eTooSmall异常" << endl;
}
catch (...)
{
}
system("pause");
}
8. 标准程序库异常
c++标准提供了一组标准异常类,这些类以基类Exception开始,标准程序库抛出的所有异常,都派生于该基类。该基类提供一个成员函数what(),用于返回错误信息(返回类型为const char *)。在Excpetion类中,what()函数的声明如下
virtual const char * what() const throw();
该函数可以在派生类中重定义。
#include "iostream"
using namespace std;
class MyException :public exception
{
public:
MyException(const char *p)
{
this->m_p =const_cast<char *>(p);
}
virtual const char *what()
{
cout <<"MyException类型:p-" << m_p <<endl;
return m_p;
}
protected:
char *m_p;
};
void testMyExeption()
{
throw MyException("函数异常");
}
void main()
{
try
{
testMyExeption();
}
catch (MyException &e)
{
e.what();
}
system("pause");
}
十一、 输入和输出流
1. i/o流的概念和流类库的结构
c++输入输出包含以下三个方面的内容。对系统指定的标准设备的输入和输出。输出到显示器屏幕,这种输入输出称为标准的输入和输出,简称标准i/o。以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据。数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件i/o。以内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串i/o。
c++的i/o对c的发展,类型安全和扩展性。在c语言中,用printf和scanf进行输入输出,往往不能保证所输入输出的数据是可靠的安全的。在c++的输入输出中,编译系统对数据类型进行严格的检查,凡是类型不正确的数据都不可能通过编译。因此c++的i/o操作是类型安全(type safe)的。c++的i/o操作是可扩展的,不仅可以用来输入输出标准类型的数据,也可以用于用户自定义类型的数据。
c++通过i/o类库实现丰富的i/o功能。这样使c++的输入输出明显地优于c语言中的printf和scanf,但是也为之付出了代价,c++的i/o系统变得比较复杂。
c++编译系统提供了用于输入输出的iostream类库。
2. 标准输入流
标准输入流对象cin。重要函数
cin.get();//一次只能读取一个字符
cin.get(一个参数);//读一个字符
cin.get(三个参数);//可以读字符串
cin.getline();
cin.ignore();
cin.peek();
cin.putback();
void main5()
{
char buf1[256];
char buf2[256];
cin >> buf2;
cin.ignore(2);
int i1 = cin.peek();
cout << i1 <<endl;
cin.getline(buf1, 256);
cout <<"buf1:" << buf1 << endl;
cout <<"buf2:" << buf2 << endl;
system("pause");
}
void main4()
{
char buf1[256];
char buf2[256];
cin >> buf2;
cin.getline(buf1, 256);
cout <<"buf1:" << buf1 << endl;
cout <<"buf2:" << buf2 << endl;
system("pause");
}
void main3()
{
char a, b, c;
cin.get(a);
cin.get(b);
cin.get(c);
cout << a << b<< c << endl;
system("pause");
}
void main2()
{
char ch;
while ((ch = cin.get()) !=EOF)
{
cout << ch<< endl;
}
system("pause");
}
void main1()
{
char buf1[1024];
int i1;
long l1;
cin >> i1;
cin >> l1;
cin >> buf1;//遇到空格停止接收
cout << i1 <<endl;
cout << l1 <<endl;
cout << buf1<< endl;
system("pause");
}
3. 标准输出函数
在输出函数时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出。如果要求以十六进制或八进制形式输出一个整数,对输出的小数只保留两位小数等。
void main()
{
cout <<"<start>";
cout.width(30);
cout.fill('*');
cout.setf(ios::showbase);
cout.setf(ios::internal);
cout << hex <<123 << "<end>\n";
cout <<"<start>"
<<setw(30)
<<setfill('*')
<<setiosflags(ios::showbase)
<<setiosflags(ios::internal)
<< hex
<< 123
<<"<end>\n";
system("pause");
}
void main6()
{
cout.put('a').put('b').put('c').put('\n');
char *p ="abcdswfefwse";
cout.write(p, strlen(p));
cout << endl;
cout.write(p, strlen(p) -4);
cout << endl;
system("pause");
}
4. 文件流和文件流对象
输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。从磁盘读,向磁盘写。和文件有关系的输入输出类主要在fstream.h这个头文件中被定义,在这个头文件中主要被定义了三个类。ifstream,ofstream,fstream,其中fstream是由iostream类派生出来的。由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在fstream.h头文件中。
c++文件的打开 与关闭。
打开文件是一种形象的说法。是指在文件读写之前做必要的准备工作,为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件;指定文件的工作方式,如,该文件是作为输入文件还是输出文件,是ASCII文件还是二进制文件等。
通过两种不同的方式打开文件。调用文件流成员函数open。在定义文件流对象时指定参数。
class Dog
{
public:
Dog()
{
a = 32;
strcpy(name,"");
}
Dog(int a, char *name)
{
this->a = a;
strcpy(this->name,name);
}
public:
void print()
{
cout << a<< "--" << name << endl;
}
private:
int a;
char name[32];
};
void main()
{
char *fname ="h://vv//cppiob.txt";
ofstream fout(fname,ios::binary);
Dog d1(12,"tom"), d2(23, "lily");
fout.write((char*)&d1, sizeof(d1));
fout.write((char*)&d2, sizeof(d2));
fout.close();
ifstream fin(fname);
Dog dtmp;
fin.read((char*)&dtmp,sizeof(Dog));
dtmp.print();
fin.read((char*)&dtmp, sizeof(Dog));
dtmp.print();
fin.close();
system("pause");
}
void main8()
{
char *fname ="h://vv//cppio4.txt";
//ofstreamfout(fname,ios::app|ios::ate);
ofstream fout(fname,ios::app);
if (!fout)
{
cout <<fname << "打开文件失败" << endl;
}
fout << "hellocpp" << endl;
fout <<"hlsjer" << endl;
fout.close();
ifstream fin(fname);
char ch;
while (fin.get(ch))
{
cout << ch;
}
system("pause");
}
十二、 stl
1. 概念
standard template library,标准模板库,是惠普实验室开发的一系列软件的统称。从广义上讲分为三类,algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝连接。几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在c++标准中,stl被组织为下面的13个头文件,<algorithm>、<deque>、<functional>、<iterator>、<list>、<vector>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。
使用stl的好处,stl是c++的一部分,因此不用额外安装什么,它被内建在编译器内;stl的一个重要特点是数据结构和算法的分离;可以不用思考stl实现的过程,能够使用stl就可以;stl具有高重用性,高性能,可移植,跨平台的特点。
stl容器允许重复利用已有的实现构造自己的特定类型下的数据结构,通过设置一些模板,stl容器对最常用的数据结构提供了支持,这些模板的参数允许我们指定容器中元素的数据类型。
例子:
class Teacher
{
public:
int age;
char name[36];
public:
void print()
{
cout <<"age:" << age << "--name:" << name<< endl;
}
};
void test2()
{
Teacher t1, t2, t3;
t1.age = 12;
t2.age = 13;
t3.age = 15;
vector<Teacher> v1;
v1.push_back(t1);
v1.push_back(t2);
v1.push_back(t3);
for(vector<Teacher>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout <<it->age << "\t";
}
cout << endl;
//添加指针
vector<Teacher *>v2;
Teacher * p1, *p2, *p3;
p1 = &t1;
p2 = &t2;
p3 = &t3;
v2.push_back(p1);
v2.push_back(p2);
v2.push_back(p3);
for (vector< Teacher*>::iterator it = v2.begin(); it != v2.begin(); it++)
{
cout <<(*it)->age << endl;
}
}
void test1()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(3);
v1.push_back(5);
v1.push_back(3);
for(vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout <<*it << "\t";
}
cout << endl;
int num1 =count(v1.begin(), v1.end(), 3);
cout << num1<< endl;
}
void main()
{
test2();
system("pause");
}
容器是用来管理一组元素的。
容器的分类
序列式容器,每个元素都有固定位置,取决于插入时机和地点,和元素无关。
关联式容器,元素位置取决于特定的排序准则,和插入顺序无关。
2. stl的string
string是stl的字符串类型,通常用来表示字符串,而在使用string之前,字符串通常是用char*表示的,string与char*都可以用来表示字符串。string和char*的比较,string是一个类,char*是一个指向字符串的指针。string封装了char*,管理这个字符串,是一个char*型的容器。string不用考虑内存释放和越界。string管理char*所分配的内存。每一次string的复制,取值都由string类型负责维护,不用担心复制越界和取值越界。string提供了一系列的字符串操作函数。
#define _SCL_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "string"
#include "algorithm"
void teststr1()
{
string s1 ="abcds";
strings2("ewerwer");
string s3 = s2;
string s4(5, 'a');
cout << s1 <<endl;
cout << s2 <<endl;
cout << s3 <<endl;
cout << s4 <<endl;
}
void teststr2()
{
string s1 ="asdfssdfe";
for (int i = 0; i <s1.length(); i++)
{
cout <<s1[i] << "\t";
}
cout << endl;
for (string::iterator it =s1.begin(); it != s1.end(); it++)
{
cout <<*it << "\t";
}
cout << endl;
for (int i = 0; i <s1.length(); i++)
{
cout <<s1.at(i) << "\t";
}
cout << endl;
}
void teststr3()
{
string s1 ="asdf";
//string>>char *
cout << s1.c_str()<< endl;
//s1的内容copy到buf1
char buf1[128] = { 0 };
s1.copy(buf1, 3, 0);
cout << buf1<< endl;
}
//字符串的连接
void teststr4()
{
string s1 ="abc";
string s2 ="def";
s1 = s1 + s2;
cout << s1 <<endl;
string s3 ="aa";
string s4 ="bb";
s3.append(s4);
cout << s3 <<endl;
}
//字符串的查找和替换
void teststr5()
{
string s1 = "wbm sdfwbm asd wbm 234 wbm we";
int index =s1.find("wbm", 0);
cout << index<< endl;
while (index != s1.npos)
{
cout <<index << endl;
index += 1;
index =s1.find("wbm", index);
}
index =s1.find("wbm", 0);
while (index != s1.npos)
{
cout <<index << endl;
s1.replace(index,3, "WBM");
index += 1;
index =s1.find("wbm", index);
}
cout << "s1替换后的结果为:" << s1 << endl;
}
//截断(区间删除)和插入
void teststr6()
{
string str1 = "hello1hello2 hello3";
string::iterator it =find(str1.begin(), str1.end(), '1');
if (it != str1.end())
{
str1.erase(it);
}
cout << str1<< endl;
str1.erase(str1.begin(),str1.end());
cout << str1<< endl;
string str2 ="abc";
str2.insert(0,"efd");
str2.insert(str2.length(),"zzz");
cout << str2<< endl;
}
//其他算法
void teststr7()
{
string s1 ="AAAbbb";
transform(s1.begin(),s1.end(),s1.begin(),toupper);
cout << s1 <<endl;
transform(s1.begin(),s1.end(), s1.begin(), tolower);
cout << s1 <<endl;
}
void main()
{
teststr7();
system("pause");
}
3. vector容器
vector是将元素置于一个动态数组中加以管理的容器。vector可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法)。vector尾部添加或删除元素非常快速,但是在中间后头部添加或插入元素比较费时。
void testv1()
{
vector<int> v1;
cout <<"length:" << v1.size() << endl;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
cout << v1.front()<< endl;
v1.front() = 10;
cout <<"length:" << v1.size() << endl;
while (v1.size() > 0)
{
cout <<v1.back() << endl;
v1.pop_back();
}
}
void testv2()
{
vector<int> v1;
cout <<"length:" << v1.size() << endl;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
vector<int> v2 = v1;
while (v2.size() > 0)
{
cout <<v2.back() << endl;
v2.pop_back();
}
vector<int> v3(v1.begin(),v1.end());
}
vector的迭代器
迭代器是一个可遍历stl容器内全部或部分元素的对象。迭代器指出容器中的一个特定位置。迭代器就如同一个指针。迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。
迭代器的类别
输入迭代器,也叫只读迭代器,从容器中读取元素,只能一次读入一个元素向前移动,只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。
输出迭代器,也叫只写迭代器,往容器中写入元素,只能一次写入一个元素向前移动,只支持一遍算法,同一个输出迭代器不能两遍遍历一个序列。
正向迭代器,组合输入迭代器和输出迭代器的功能,可以多次解析一个迭代器指定的位置,可以对一个值进行多次读/写。
双向迭代器,组合正向迭代器的功能,可以通过—操作符向后移动位置。
随机访问迭代器,组合双向迭代器的功能,可以向前后跳过任意个位置,可以直接访问容器中任何位置的元素。
void testv3()
{
vector<int> v1(10);
for (int i = 0; i < 10;i++)
{
v1[i] = i;
}
for (vector<int>::iteratorit = v1.begin(); it != v1.end(); it++)
{
cout <<*it << endl;
}
cout << "逆序遍历" << endl;
for(vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++)
{
cout <<*it << endl;
}
}
删除和插入
void printV(vector<int> &v)
{
for (int i = 0; i <v.size(); i++)
{
cout <<v[i] << " ";
}
cout << endl;
}
void testv4()
{
vector<int> v1(10);
for (int i = 0; i < 10;i++)
{
v1[i] = i;
}
v1.erase(v1.begin(),v1.begin() + 3);
v1.erase(v1.begin());
printV(v1);
v1[2] = 2;
v1[3] = 2;
printV(v1);
//找到所有值为2的元素
for(vector<int>::iterator it = v1.begin(); it != v1.end(); )
{
if (*it == 2)
{
it =v1.erase(it);//当删除迭代指向的元素的时候,迭代器会自动++
}
else
{
it++;
}
}
printV(v1);
v1.insert(v1.begin(),100);
v1.insert(v1.end(), 20);
printV(v1);
}
4. deque容器
双端数组。
void printD(deque<int> &d)
{
for(deque<int>::iterator it = d.begin(); it != d.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
void testd1()
{
deque<int> d1;
d1.push_back(1);
d1.push_back(2);
d1.push_back(3);
d1.push_front(10);
d1.push_front(20);
d1.push_front(30);
printD(d1);
d1.pop_back();
d1.pop_front();
printD(d1);
deque<int>::iteratorit = find(d1.begin(), d1.end(), 10);
if (it != d1.end())
{
cout <<distance(d1.begin(),it) << endl;
}
else
{
cout <<"没有找到值为10的元素" << endl;
}
}
5. stack栈模型
stack是堆栈容器,是一种先进后出的容器。stack是简单地装饰deque容器而成为另外一种容器。
void tests1()
{
stack<int> s1;
for (int i = 0; i < 10;i++)
{
s1.push(i);
}
cout << "栈的大小:" << s1.size() << endl;
while (!s1.empty())
{
int tmp = s1.top();
cout <<tmp << " ";
s1.pop();
}
}
class Teacher
{
public:
int age;
char *name;
public:
void printT()
{
cout <<"age" << age << endl;
}
};
void tests2()
{
Teacher t1, t2, t3;
t1.age = 12;
t2.age = 13;
t3.age = 15;
stack<Teacher> s1;
s1.push(t1);
s1.push(t2);
s1.push(t3);
cout << "栈的大小:" << s1.size() << endl;
while (!s1.empty())
{
Teacher tmp =s1.top();
tmp.printT();
s1.pop();
}
}
void tests3()
{
Teacher t1, t2, t3;
t1.age = 12;
t2.age = 13;
t3.age = 15;
stack<Teacher *> s1;
s1.push(&t1);
s1.push(&t2);
s1.push(&t3);
cout << "栈的大小:" << s1.size() << endl;
while (!s1.empty())
{
Teacher * tmp =s1.top();
tmp->printT();
s1.pop();
}
}
6. queue容器
queue是队列容器,是一种先进先出的容器。
class Teacher
{
public:
int age;
char *name;
public:
void printT()
{
cout <<"age" << age << endl;
}
};
void testq1()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << "队列首元素:" << q.front() << endl;
cout << "队列的大小:" << q.size() << endl;
while (!q.empty())
{
cout <<q.front() << " ";
q.pop();
}
cout << endl;
}
void testq2()
{
Teacher t1, t2, t3;
t1.age = 12;
t2.age = 13;
t3.age = 15;
queue<Teacher> q1;
q1.push(t1);
q1.push(t2);
q1.push(t3);
cout << "队列首元素:" << q1.front().age << endl;
cout << "队列的大小:" << q1.size() << endl;
while (!q1.empty())
{
Teacher tmp =q1.front();
tmp.printT();
q1.pop();
}
cout << endl;
}
void testq3()
{
Teacher t1, t2, t3;
t1.age = 12;
t2.age = 13;
t3.age = 15;
queue<Teacher *> q1;
q1.push(&t1);
q1.push(&t2);
q1.push(&t3);
cout << "队列首元素:" << q1.front()->age << endl;
cout << "队列的大小:" << q1.size() << endl;
while (!q1.empty())
{
Teacher * tmp =q1.front();
tmp->printT();
q1.pop();
}
cout << endl;
}
7. list容器
list是一个双向列表容器,可高效地进行插入删除元素。list不可以随机存取元素,所以不支持at.(pos)函数与[]操作。
void testl1()
{
list<int> l1;
cout << "list长度" << l1.size() << endl;
for (int i = 0; i < 10;i++)
{
l1.push_back(i);
}
cout << "list长度" << l1.size() << endl;
list<int>::iteratorit = l1.begin();
while (it != l1.end())
{
cout <<*it << " ";
it++;
}
cout << endl;
it = l1.begin();
it++;
it++;
it++;
l1.insert(it, 100);
for(list<int>::iterator it = l1.begin(); it != l1.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
void printL(list<int> &l)
{
list<int>::iteratorit = l.begin();
while (it != l.end())
{
cout <<*it << " ";
it++;
}
cout << endl;
}
void testl2()
{
list<int> l1;
cout << "list长度" << l1.size() << endl;
for (int i = 0; i < 10;i++)
{
l1.push_back(i);
}
cout << "list长度" << l1.size() << endl;
list<int>::iteratorit1 = l1.begin();
list<int>::iteratorit2 = l1.begin();
it2++;
it2++;
it2++;
l1.erase(it1, it2);
printL(l1);
l1.insert(l1.begin(),100);
l1.insert(l1.begin(),100);
l1.insert(l1.begin(),100);
printL(l1);
l1.remove(100);
printL(l1);
}
8. priority_queue
优先级队列。最大值优先级队列,最小值优先级队列。
void testq1()
{
priority_queue<int>q1;//默认情况下,最大值优先队列
priority_queue<int,vector<int>, less<int>> q2;//预先定义好的预定义函数,谓词
//priority_queue<int,vector<int>, greater<int>> q3;//最小值优先队列
q1.push(12);
q1.push(123);
q1.push(5);
q1.push(16);
cout << "队头元素:" << q1.top() << endl;
cout << "队列的大小:" << q1.size() << endl;
while (q1.size() > 0)
{
cout <<q1.top() << " ";
q1.pop();
}
}
9. set/multiset
set是一个集合容器,其中包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。
set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。set不可以直接存取元素。不可以使用at.(pos)与[]操作符。
multiset与set的区别,set支持唯一键值,每个元素值只能出现一次,而multiset中同一值可以出现多次。
不可以直接修改set或multiset容器中的元素值,因为该容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。
set的例子
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "set"
class Student
{
public:
Student(char *name, intage)
{
this->age =age;
strcpy(this->name,name);
}
public:
char name[64];
int age;
};
struct FuncStudent
{
bool operator()(constStudent &stu1,const Student &stu2)
{
if (stu1.age< stu2.age)
{
returntrue;
}
else
{
returnfalse;
}
}
};
void printS(set<int> &s)
{
for(set<int>::iterator it = s.begin(); it != s.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
void eraseS(set<int> &s)
{
while (!s.empty())
{
set<int>::iteratorit = s.begin();
cout <<*it << " ";
s.erase(it);
}
cout << endl;
}
void tests3()
{
set<Student,FuncStudent>s1;
Studentst1("wangwu1", 13);
Studentst2("wangwu2", 23);
Student st3("wangwu3",16);
Studentst4("wangwu4", 63);
Studentst5("wangwu4", 13);
pair<set<Student,FuncStudent>::iterator,bool> pair1 = s1.insert(st1);
if (pair1.second == true)
{
cout <<"插入st1成功" << endl;
}
else
{
cout <<"插入st1失败" << endl;
}
s1.insert(st2);
s1.insert(st3);
s1.insert(st4);
pair<set<Student,FuncStudent>::iterator, bool> pair5 = s1.insert(st5);
if (pair5.second == true)
{
cout <<"插入st5成功" << endl;
}
else
{
cout <<"插入st5失败" << endl;
}
for (set<Student,FuncStudent>::iterator it = s1.begin(); it != s1.end(); it++)
{
cout <<it->age << "--" << it->name << endl;
}
}
void tests1()
{
set<int> s1;
for (int i = 0; i < 5;i++)
{
int tmp =rand();
s1.insert(tmp);
}
s1.insert(100);
s1.insert(100);
printS(s1);
eraseS(s1);
cout << "s1的长度:" << s1.size() << endl;
}
void tests2()
{
set<int> s1;
set<int,less<int>>s2;
for (int i = 0; i < 5;i++)
{
int tmp =rand();
s2.insert(tmp);
}
for (set<int,less<int>>::iterator it = s2.begin(); it != s2.end(); it++)
{
cout << *it<< " ";
}
cout << endl;
}
void tests4()
{
set<int> s1;
for (int i = 0; i < 10;i++)
{
s1.insert(i);
}
printS(s1);
set<int>::iteratorit1 = s1.find(5);
cout << "找的的元素" << *it1 << endl;
int num1 = s1.count(5);
cout << "值为5的元素的个数:" << num1 << endl;
set<int>::iteratorit2 = s1.lower_bound(5);
cout << "第一个值大于等于5的元素:" << *it2 << endl;
set<int>::iteratorit3 = s1.upper_bound(5);
cout << "第一个值大于5的元素:" << *it3 << endl;
pair<set<int>::iterator,set<int>::iterator> p1 = s1.equal_range(5);
set<int>::iteratorit4 = p1.first;
set<int>::iteratorit5 = p1.second;
cout << *it4<< endl;
cout << *it5<< endl;
}
void main()
{
tests4();
system("pause");
}
multiset的例子
void testms1()
{
multiset<int> ms1;
cout << "请输入ms1的值,当输入0后可以结束插入" << endl;
int tmp;
cin >> tmp;
while (tmp != 0)
{
ms1.insert(tmp);
cin >>tmp;
}
cout << "ms1的值为:" << endl;
for(multiset<int>::iterator it = ms1.begin(); it != ms1.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
10. map容器
map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。multimap与map的区别,map支持唯一键值,每个键只能出现一次,而multimap中相同键可以出现多次。multimap不支持[]操作符。
插入和遍历的例子
void testm1()
{
map<int, string> m1;
m1.insert(pair<int,string>(1, "zhangs"));
m1.insert(pair<int,string>(2, "wangw"));
m1.insert(make_pair(3,"lisi"));
m1.insert(make_pair(4,"zhaosi"));
m1.insert(map<int,string>::value_type(5, "tom"));
m1.insert(map<int,string>::value_type(6, "tom2"));
m1[7] = "lily";
m1[8] = "lilei";
for (map<int,string>::iterator it = m1.begin(); it != m1.end(); it++)
{
cout <<it->first << "--" << it->second << endl;
}
while (!m1.empty())
{
map<int,string>::iterator it = m1.begin();
m1.erase(it);
}
cout << "m1的长度:" << m1.size() << endl;
}
查找的例子
void testm2()
{
map<int, string> m1;
m1.insert(pair<int,string>(1, "zhangs"));
m1.insert(pair<int,string>(2, "wangw"));
m1.insert(make_pair(3,"lisi"));
m1.insert(make_pair(4,"zhaosi"));
m1.insert(map<int,string>::value_type(5, "tom"));
m1.insert(map<int,string>::value_type(6, "tom2"));
m1[7] = "lily";
m1[8] = "lilei";
map<int,string>::iterator it1 = m1.find(8);
if (it1 == m1.end())
{
cout <<"key10的值不存在" << endl;
}
else
{
cout <<"key10的值为:" << it1->second<< endl;
}
pair<map<int,string>::iterator,map<int,string>::iterator>p1 = m1.equal_range(5);
if (p1.first == m1.end())
{
cout <<"第一个>=5的迭代器不存在" << endl;
}
else
{
cout <<p1.first->first << "--" << p1.first->second<< endl;
}
if (p1.second == m1.end())
{
cout <<"第一个>=5的迭代器不存在" << endl;
}
else
{
cout <<p1.second->first << "--" << p1.second->second<< endl;
}
}
multimap案例
class Person
{
public:
int age;
string name;
string tel;
double saly;
};
void testm3()
{
Person p1, p2, p3, p4,p5;
p1.name = "张1";
p1.age = 13;
p2.name = "张2";
p2.age = 14;
p3.name = "张3";
p3.age = 15;
p4.name = "张4";
p4.age = 16;
p5.name = "找4";
p5.age = 17;
multimap<string,Person> m1;
m1.insert(make_pair("sale",p1));
m1.insert(make_pair("sale",p2));
m1.insert(make_pair("development",p3));
m1.insert(make_pair("development",p4));
m1.insert(make_pair("fanancial",p5));
for (multimap<string,Person>::iterator it = m1.begin(); it != m1.end(); it++)
{
cout <<it->first << "--" << it->second.age <<"--" << it->second.name << endl;
}
int countd =m1.count("development");
cout << "开发部人数:" << m1.count("development") << endl;
multimap<string,Person>::iterator it2 = m1.find("development");
for (int i = 0; it2 !=m1.end()&&i<countd;i++)
{
cout <<it2->first << "--" << it2->second.age <<"--" << it2->second.name << endl;
it2++;
}
for (multimap<string,Person>::iterator it = m1.begin(); it != m1.end(); it++)
{
if((it->second.age) == 13)
{
it->second.name= "修改";
}
}
for (multimap<string,Person>::iterator it = m1.begin(); it != m1.end(); it++)
{
if((it->second.age) == 13)
{
cout<< it->second.name << endl;
}
}
}
11. 容器的共性
所有的容器提供的都是值语意而非引用语意。容器执行插入元素的操作时,内部实施拷贝动作。所以,stl内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
提供拷贝构造和=操作符的例子
class Teacher
{
public:
Teacher(int age, char*name)
{
m_pname = newchar[strlen(name) + 1];
strcpy(m_pname,name);
m_age = age;
}
~Teacher()
{
if (m_pname !=NULL)
{
delete[]m_pname;
m_pname = NULL;
m_age =0;
}
}
Teacher(const Teacher&obj)
{
m_pname = newchar[strlen(obj.m_pname) + 1];
strcpy(m_pname,obj.m_pname);
m_age =obj.m_age;
}
Teacher &operator=(const Teacher &obj)
{
//先释放接收端的内存
if (m_pname !=NULL)
{
delete[]m_pname;
m_pname= NULL;
m_age =0;
}
//根据参数分配内存大小
m_pname = newchar[strlen(obj.m_pname) + 1];
strcpy(m_pname,obj.m_pname);
m_age =obj.m_age;
return *this;
}
public:
void printT()
{
cout <<m_age << "--" << m_pname << endl;
}
private:
char * m_pname;
int m_age;
};
void test1()
{
Teacher t1(12,"wangw");
vector<Teacher> v1;
v1.push_back(t1);
v1[0].printT();
}
void main()
{
test1();
system("pause");
}
12. 容器的使用时机
| vector | deque | list | set | multiset | map | multimap |
典型内存结构 | 单端数据 | 双端数组 | 双向链表 | 二叉树 | 二叉树 | 二叉树 | 二叉树 |
可随机存取 | 是 | 是 | 否 | 否 | 否 | 对key而言是 | 否 |
元素搜寻速度 | 慢 | 慢 | 非常慢 | 否 | 否 | 对key而言快 | 对key而言是 |
快速安插移除 | 尾端 | 头尾两端 | 任何位置 |
|
|
|
|
deque的使用场景,比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
list的使用场景,比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确定位置的移除插入。
set的使用场景,比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排序。
map的使用场景,比如按id号存储十万个用户,想要快速通过id查找对应的用户。
十三、 stl的算法
1. 概述
算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。
<algorithm>是所有stl头文件中最大的一个,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、反转、排序、合并等等。
<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。
<functional>中则定义了一些模板类,用以声明函数对象。
stl提供了大量实现算法的模板函数,只要熟悉了stl之后,许多代码可以被简化,只需通过调用一两个算法模板,就可以完成所需要的功能,从而大大提升效率。
2. stl算法分类
操作对象,直接改变容器的内容;将原容器的内容复制一份,修改其副本,然后传回该副本上。
功能,非可变序列算法,指不直接修改其所操作的容器内容的算法,计数算法,搜索算法,比较算法;可变序列算法,指可以修改它们所操作的容器内容的算法,删除算法,修改算法,排序算法;排序算法,包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作;数值算法,对容器内容进行数值计算。
3. 算法中的函数对象和谓词
函数对象是重载函数调用操作符的类,其对象常称为函数对,是类似函数的对象。一个类对象表现出一个函数的特征,就是通过对象名+参数列表的方式使用一个类对象。如果没有上下文,完全可以把它看做一个函数对待。这是通过重载类的operator()来实现的。
template <typename T>
class ShowElem
{
public:
ShowElem()
{
n = 0;
}
void operator()(T &t)
{
n++;
cout << t<< " ";
}
void printN()
{
cout <<"n:" << n << endl;
}
private:
int n;
};
void FuncShowElem(int &i)
{
cout << i <<" ";
}
void test1()
{
int a = 10;
ShowElem<int> s1;
s1(a);
}
void test2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for_each(v1.begin(),v1.end(), ShowElem<int>());
cout << endl;
for_each(v1.begin(),v1.end(), FuncShowElem);
cout << endl;
ShowElem<int> s1;
for_each(v1.begin(),v1.end(), s1);
s1.printN();
cout << "通过for_each方法的返回值查看调用的次数" << endl;
s1 = for_each(v1.begin(),v1.end(), s1);
s1.printN();
}
void main()
{
test2();
system("pause");
}
4. 一元函数和一元谓词
一元函数对象,函数参数是1个。一元谓词,函数参数是1个,函数返回值是bool类型,可以作为一个判断式,谓词可以是一个仿函数,也可以是一个回调函数。二元谓词,函数参数是2个,函数返回值是bool类型。
template <typename T>
class IsDiv
{
public:
IsDiv(const T&divisor)
{
this->divisor= divisor;
}
bool operator()(T &t)
{
return(t%divisor == 0);
}
private:
T divisor;
};
void test3()
{
vector<int> v1;
for (int i = 0; i < 33;i++)
{
v1.push_back(i);
}
int a = 4;
IsDiv<int> d1(a);
vector<int>::iteratorit = find_if(v1.begin(), v1.end(), IsDiv<int>(a));
if (it != v1.end())
{
cout <<"容器中第一个值被4整除的元素为:" << *it << endl;
}
else
{
cout <<"容器中没有值被4整除的元素" << endl;
}
}
void main()
{
test3();
system("pause");
}
二元函数和二元谓词的例子
template <typename T>
class SumAdd
{
public:
T operator()(T t1, T t2)
{
return t1 + t2;
}
};
void test4()
{
vector<int> v1, v2;
vector<int> v3;
v1.push_back(1);
v1.push_back(3);
v1.push_back(5);
v2.push_back(2);
v2.push_back(4);
v2.push_back(6);
v3.resize(10);
transform(v1.begin(),v1.end(), v2.begin(), v3.begin(), SumAdd<int>());
for(vector<int>::iterator it = v3.begin(); it != v3.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
bool MyCompare(const int &a,const int &b)
{
return a < b;
}
void ShowNum(const int &i)
{
cout << i <<" ";
}
void test5()
{
vector<int> v(10);
for (int i = 0; i < 10;i++)
{
int tmp = rand()% 100;
v[i] = tmp;
}
for (vector<int>::iteratorit = v.begin(); it != v.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
sort(v.begin(), v.end(),MyCompare);
for_each(v.begin(),v.end(), ShowNum);
cout << endl;
}
void main()
{
test5();
system("pause");
}
5. 二元谓词在set集合中的应用
struct CompareNoCase
{
bool operator()(conststring &s1, const string &s2)
{
string _s1;
_s1.resize(s1.size());
transform(s1.begin(),s1.end(), _s1.begin(), tolower);
string _s2;
_s2.resize(s2.size());
transform(s2.begin(),s2.end(), _s2.begin(), tolower);
return (_s1 >_s2);
}
};
void test6()
{
set<string> s1;
s1.insert("abc");
s1.insert("edf");
s1.insert("jgh");
set<string>::iteratorit = s1.find("aBc");
if (it == s1.end())
{
cout <<"没有找到" << endl;
}
else
{
cout <<"找到了" << endl;
}
set<string,CompareNoCase> s2;
s2.insert("bbb");
s2.insert("ccc");
s2.insert("ddd");
set<string,CompareNoCase>::iterator it2 = s2.find("BbB");
if (it2 == s2.end())
{
cout <<"没有找到" << endl;
}
else
{
cout <<"找到了" << endl;
}
}
6. 预定义函数对象和函数适配器
标准模板库stl提前定义了很多预定义函数对象。预定义的函数对象支持算数运算。调用的操作符与type相关联的实例。支持关系函数对象和逻辑函数对象。
void test11()
{
plus<int> add;
int a = 10;
int b = 20;
int c = add(a, b);
cout << c <<endl;
plus<string> adds;
string s1 ="abc";
string s2 ="def";
string s3 = adds(s1, s2);
cout << s3 <<endl;
vector<string> v1;
v1.push_back("bbb");
v1.push_back("ddd");
v1.push_back("aa");
v1.push_back("ddd");
v1.push_back("ddd");
sort(v1.begin(), v1.end(),greater<string>());
for(vector<string>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
string s4 ="ddd";
int num =count_if(v1.begin(), v1.end(), bind2nd(equal_to<string>(),s4));
cout << num <<endl;
}
stl中已经定义了大量的函数对象,但是有时候需要对函数返回值进行进一步的简单计算,或者填上多余的参数,不能直接代入算法。函数适配器实现了这一功能,将一种函数对象转化为另一种符合要求的函数对象。函数适配器可以分为4大类,绑定适配器、组合适配器、指针函数适配器和成员函数适配器。
常用的函数适配器,有4个。绑定器binder,通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换成一元函数对象。c++标准库提供两种预定义的binder适配器,bind1st和bind2nd,前者把值绑定到二元函数对象的第一个实参上,后者绑定到第二个实参上。取反器negator,将函数对象的值翻转到函数适配器,stl提供两个预定义的negator,not1翻转一元预定义函数对象的真值,而not2翻转二元谓词函数的真值。
struct IsGreater
{
public:
IsGreater(int num)
{
this->num =num;
}
bool operator()(int num2)
{
if (num >num2)
{
returnfalse;
}
else
{
returntrue;
}
}
private:
int num;
};
void test12()
{
vector<int> v1;
for (int i = 0; i < 10;i++)
{
v1.push_back(i +1);
}
for(vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
int n1 = count(v1.begin(),v1.end(), 3);
cout << n1 <<endl;
int n2 = count_if(v1.begin(),v1.end(), IsGreater(2));
cout << n2 <<endl;
int n3 =count_if(v1.begin(), v1.end(), bind2nd(greater<int>(), 2));
cout << n3 <<endl;
int n4 =count_if(v1.begin(), v1.end(), bind2nd(modulus<int>(), 2));
cout << n4 <<endl;
int n5 = count_if(v1.begin(),v1.end(), not1(bind2nd(modulus<int>(), 2)));
cout << n5 <<endl;
}
7. for_each
用指定函数依次对指定范围内所有元素进行迭代访问。该函数不得修改序列中的元素。
void printV(vector<int> &v)
{
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
class MyShow
{
public:
MyShow()
{
num = 0;
}
public:
void operator()(int&n)
{
num++;
cout << n<< " ";
}
void printNum()
{
cout <<num << endl;
}
private:
int num;
};
void showvector(int &i)
{
cout << i <<" ";
}
void test21()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
printV(v1);
for_each(v1.begin(),v1.end(), showvector);
cout << endl;
for_each(v1.begin(),v1.end(), MyShow());
cout << endl;
MyShow m1 =for_each(v1.begin(), v1.end(), MyShow());
m1.printNum();
}
8. transform
与for_each类似,遍历所有元素,但可对容器的元素进行修改。如果目标与源相同,transform和for_each一样。如果想以某值替换符合规则的元素,应使用replace算法。
void printL(list<int> &l)
{
for(list<int>::iterator it = l.begin(); it != l.end(); it++)
{
cout <<*it << " ";
}
cout << endl;
}
int increase(int i)
{
return i + 1;
}
void test22()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
printV(v1);
transform(v1.begin(),v1.end(), v1.begin(), increase);
printV(v1);
transform(v1.begin(),v1.end(), v1.begin(), negate<int>());
printV(v1);
list<int> l1;
l1.resize(v1.size());
transform(v1.begin(),v1.end(), l1.begin(), bind2nd(multiplies<int>(),10));
printL(l1);
transform(v1.begin(),v1.end(), ostream_iterator<int>(cout, ""),negate<int>());
}
9. 常用的查找算法
adjacent_find,在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的迭代器。否则返回past-the-end。
void test23()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
printV(v1);
vector<int>::iteratorit1 = adjacent_find(v1.begin(), v1.end());
if (it1 == v1.end())
{
cout <<"没找到" << endl;
}
else
{
cout <<"找到了" << endl;
}
int index =distance(v1.begin(), it1);
cout << "下标为:" << index << endl;
}
binary_search,在有序序列中查找value找到返回true。注意,在无序列中,不可使用。
void test24()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
v1.push_back(5);
v1.push_back(7);
v1.push_back(9);
v1.push_back(11);
bool b1 =binary_search(v1.begin(),v1.end(),5);
if (b1)
{
cout <<"找到了" << endl;
}
else
{
cout <<"没有找到" << endl;
}
}
count,利用等于操作符,把标志范围内的元素与输入值比较,返回相等的个数。
void test25()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
int c1 = count(v1.begin(),v1.end(), 2);
cout << c1 <<endl;
}
count_if,使用比较函数进行计数。
void test26()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
v1.push_back(13);
v1.push_back(32);
int c1 =count_if(v1.begin(), v1.end(), IsGreater);
cout << c1 <<endl;
}
find_if,使用输入的函数代替等于操作符执行find。返回被找到的元素的迭代器。
void test27()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
v1.push_back(13);
v1.push_back(32);
vector<int>::iteratorit = find(v1.begin(), v1.end(), 2);
if (it == v1.end())
{
cout <<"没找到" << endl;
}
else
{
cout <<"找到了" << endl;
}
}
10. 排序算法
merge,合并两个有序序列,存放到另一个序列。
void test28()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
vector<int> v2;
v2.push_back(11);
v2.push_back(25);
v2.push_back(23);
v2.push_back(23);
vector<int> v3;
v3.resize(v1.size()+v2.size());
cout << v3.size()<< endl;
merge(v1.begin(),v1.end(), v2.begin(), v2.end(), v3.begin());
printV(v3);
}
sort,以默认升序的方式重新排列指定范围内的元素。若要改排序规则,可以输入比较函数。
bool CompareS(Student &s1, Student &s2)
{
return (s1.m_id >s2.m_id);
}
void test29()
{
vector<Student> v1;
Students1("tom", 12);
Students2("zhangs", 22);
Students3("lisi", 16);
Students4("zhaol", 5);
v1.push_back(s1);
v1.push_back(s2);
v1.push_back(s3);
v1.push_back(s4);
for (vector<Student>::iteratorit = v1.begin(); it != v1.end(); it++)
{
cout <<it->m_id << "--" << it->m_name << endl;
}
sort(v1.begin(), v1.end(),CompareS);
for(vector<Student>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout <<it->m_id << "--" << it->m_name << endl;
}
}
random_shuffle,对指定范围内的元素随机调整次序。
void test30()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(22);
v1.push_back(35);
v1.push_back(12);
v1.push_back(27);
v1.push_back(30);
printV(v1);
random_shuffle(v1.begin(),v1.end());
printV(v1);
}
reverse,将指定范围内地的元素反转。
void test31()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(22);
v1.push_back(35);
printV(v1);
reverse(v1.begin(),v1.end());
printV(v1);
}
11. 拷贝和替换
copy
void test32()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(22);
vector<int> v2;
v2.resize(v1.size());
copy(v1.begin(), v1.end(),v2.begin());
printV(v2);
}
replace,把指定范围内的所有等于oldValue的元素替换成newValue。
void test33()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(22);
v1.push_back(2);
v1.push_back(22);
v1.push_back(2);
v1.push_back(22);
replace(v1.begin(),v1.end(), 2, 20);
printV(v1);
}
replace_if,把指定范围内所有操作结果为true的元素用新值替换。
bool GreaterTwo(int num)
{
if (num > 2)
{
return true;
}
else
{
return false;
}
}
void test34()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(22);
v1.push_back(2);
v1.push_back(22);
replace_if(v1.begin(),v1.end(), GreaterTwo, 100);
printV(v1);
}
swap,交换两个容器的元素。
void test35()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
vector<int> v2;
v2.push_back(10);
v2.push_back(20);
v2.push_back(100);
swap(v1, v2);
printV(v1);
printV(v2);
}
12. 算数和生成算法
accumulate,对指定范围内的元素求和,然后结果再加上一个由val指定的初始值。
void test36()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
int num =accumulate(v1.begin(), v1.end(), 100);
cout << num <<endl;
}
fill,将输入值赋给标志范围内的所有元素。
void test37()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
fill(v1.begin(), v1.end(),100);
printV(v1);
}
13. 常用集合算法
set_union,构造一个有序序列,包含两个有序序列的并集。
set_intersection,构造一个有序序列,包含两个有序序列的交集。
set_difference,构造一个有序序列,该序列保留第一个有序序列中存在而第二个有序序列中不存在的元素。
void test38()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(8);
v1.push_back(23);
vector<int> v2;
v2.push_back(8);
v2.push_back(23);
v2.push_back(8);
v2.push_back(23);
vector<int> v3;
v3.resize(v1.size() +v1.size());
set_union(v1.begin(),v1.end(), v2.begin(), v2.end(), v3.begin());
printV(v3);
}
14. 模拟比赛进程案例
选手24名,进行3轮比赛,10位裁判打分,去掉最低最高分取平均分。第一轮,抽签编号,分为4组,每组6人,每组决出前3名,进入第二轮。第二轮,抽签编号,分为2组,每组6人,每组决出前3名,进入决赛。决赛,6人,决出前3名。
#include "iostream"
using namespace std;
#include "string"
#include "vector"
#include "list"
#include "set"
#include "map"
#include "deque"
#include "algorithm"
#include "functional"
#include "iterator"
#include "numeric"
class Speaker
{
public:
string s_name;
int s_score[3];
};
int GenSpeaker(map<int, Speaker> &mSpeaker, vector<int>&v)
{
string str ="abcdefghijklmnopqrstuvwxyz";
random_shuffle(str.begin(),str.end());
for (int i = 0; i < 24;i++)
{
Speaker tmp;
string s_name ="选手";
tmp.s_name =s_name + str[i];
mSpeaker.insert(pair<int,Speaker>(100 + i, tmp));
//cout <<tmp.s_name << endl;
}
for (int i = 0; i < 24;i++)
{
v.push_back(100+ i);
}
return 0;
}
int speaker_contest_draw(vector<int> &v)
{
random_shuffle(v.begin(),v.end());
return 0;
}
int speaker_contest(int round, vector<int> &v1, map<int,Speaker> &mSpeaker, vector<int> &v2)
{
multimap<int, int,greater<int>> multimapGroup;
int tmpCount = 0;
for(vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
tmpCount++;
deque<int>dscore;
for (int i = 0;i < 10; i++)
{
intscore = 50 + rand() % 50;
dscore.push_back(score);
}
sort(dscore.begin(),dscore.end());
dscore.pop_back();
dscore.pop_front();
int scoresum =accumulate(dscore.begin(), dscore.end(),0);
int scoreavg =scoresum / dscore.size();
mSpeaker[*it].s_score[round]= scoreavg;
multimapGroup.insert(pair<int,int>(scoreavg, *it));
if (tmpCount % 6== 0)
{
cout<< "打印小组比赛成绩:" << endl;
for(multimap<int, int>::iterator it = multimapGroup.begin(); it !=multimapGroup.end(); it++)
{
cout<< it->first << "--" << it->second <<"--" << mSpeaker[it->second].s_name << endl;
}
while(multimapGroup.size() > 3)
{
multimap<int,int, greater<int>>::iterator itm = multimapGroup.begin();
v2.push_back(itm->second);
multimapGroup.erase(itm);
}
multimapGroup.clear();
}
}
return 0;
}
int speaker_contest_print(int round,vector<int> &v,map<int, Speaker> &mSpeaker)
{
cout << "第" << round+1 << "轮比赛晋级名单:" << endl;
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout <<"编号:" << *it << ";" << "姓名:" <<mSpeaker[*it].s_name << ";" << "成绩:" << mSpeaker[*it].s_score[round] << endl;
}
cout << endl;
return 0;
}
void main()
{
map<int,Speaker>mSpeaker;
vector<int> v1;//第一轮
vector<int> v2;//第二轮
vector<int> v3;//第三轮
vector<int> v4;//第四轮
//选手抽签,产生第一轮名单
GenSpeaker(mSpeaker, v1);
//第一轮,选手抽签,选手比赛,打印结果
speaker_contest_draw(v1);
speaker_contest(0, v1,mSpeaker, v2);
speaker_contest_print(0,v2,mSpeaker);
//第二轮,选手抽签,选手比赛,打印结果
speaker_contest_draw(v2);
speaker_contest(1, v2,mSpeaker, v3);
speaker_contest_print(1,v3, mSpeaker);
//第三轮,选手抽签,选手比赛,打印结果
speaker_contest_draw(v3);
speaker_contest(2, v3,mSpeaker, v4);
speaker_contest_print(2,v4, mSpeaker);
system("pause");
}