C++程序设计概念

本章分享《新标准C++程序设计》学习笔记,包括部分源码,北大郭炜编著,网上能搜到他们的系列公开课。

第一章,从C到C++

引用

引用
定义方式:类型名 & 引用名=同类型的某变量名;例如,int n;int & r=n; r成为了n的引用,r的类型是int &。
定义引用时一定要初始化,引用只能引用变量,不能是常量或表达式;引用相当于该变量的一个别名
引用作为函数的返回值:函数的返回值可以是引用,例如,int & SetValue(){return n;},函数返回对n的引用
参数传引用:如果函数的形参是引用,那么参数的传递方式就是传引用的,形参是对应实参的引用,也就是说形参和实参是一回事,形参的改变会影响实参;例如,void Swap(int & a,int & b) {int tmp;tmp=a;a=b;b=tmp;},交换ab的值
常引用:定义引用时在前面加const关键字,该引用就成为常引用,例如,int n; const int & r=n; 定义了常引用r,其类型是const int &;不能通过常引用区修改其引用的内容
示例:参数传引用

#include<iostream>
using namespace std;
void Swap(int & a, int & b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}
int main()
{
	int n1 = 100, n2 = 50;
	Swap(n1, n2);//n1、n2的值被交换
	cout << n1 << " " << n2 << endl;//输出50 100
	return 0;
}

内联函数

在返回值类型前面加inline关键字,例如,inline int MAX(int a,int b) {if(a>b) return a; return b;}
执行时不会将该语句编译成函数调用的指令,而是直接将整个函数体代码插入调用语句处;可减少函数调用的开销,但会使最终可执行程序的体积增加;增加空间消耗来节省时间,适合很简单、执行很快的函数。

指针和动态内存分配

内存分配是在程序运行中进行的,不是在编译时进行的

P=new T; T是任意类型名,P是类型为T 的指针,语句动态分配一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P
动态分配任意大小的数组,P=new T[N]; T是任意类型名,N代表元素个数,可以是任何值为正整数的表达式,这样的语句动态分配出N
sizeof(T)个字节的内存空间,这片空间的起始地址被赋值为P。

delete运算符

delete运算符,对动态分配所得的内存空间进行释放, 基本用法: delete 指针; 例如,int * p=new int;*p=5;delete p;
释放动态分配的数组,delete [ ]指针; 例如,int * p=new int[20];p[0]=0; delete [] p;
用new运算符动态分配的内存空间,一定要用delete运算符释放,否则会造成内存泄露,使系统变慢,但重启计算机可使这种情况消失。

string处理字符串

string 变量名; 可以在定义时初始化,例如,string str1;//定义了string对象str1。 string city=“Beijing”;//定义了string对象city,并对其初始化
还可以定义string对象数组,例如,string as[ ]={“Beijing”,“Shanghai”,“Chengdu”}; cout<<as[1]; //输出Shanghai、
string对象的输入输出,可以用cin、cout进行输入输出,例如,string s1, s2; cin>>s1>>s2; cout<<s1<<“,”<<s2;
string对象的赋值,string对象之间可以互相赋值,也可以用字符串常量和字符数组的名字对string对象赋值,例如,string s1, s2=“ok”; s1=“China”; s2=s1; char name[ ]=“Lady Xiong”; s1=name; //修改s1不会影响name
string对象的运算
string对象之间可以用“<“、“<=”、”==“、”>=“、“>”运算符进行比较,还可以用“+”将两个string对象相加、将一个字符串和string对象相加、将一个字符数组和string对象相加,相当于进行字符串连接。
“+=“运算符也适用于string对象,string对象还可以通过”[ ]“运算符和下标存取字符串中的某个字符 string对象是按词典序比较大小的,大小写相关,且大写字母的ASCII码小于小写字母的ASCII码
例如,string s1=“123” ,s2=“abc” ,s3; //s3是空串 s3=s1+s2; //s3变成"123abc” s3+=“de”; //s3变成"123abcde” bool b=s1<s3; //b为true char c=s1[2]; //c变成’3’,下标从0算起 s1[2]=‘5’; //s1变成"125”
示例:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1 = "123", s2;//s2是空串
	s2 += s1;
	s1 = "abc";
	s1 += "def";
	cout << "1)" << s1 << endl;
	if (s2 < s1)
		cout << "2)s2<s1" << endl;
	else
		cout << "2)s2>s1" << endl;
	s2[1] = 'A';
	s1 = "XYZ" + s2;
	string s3 = s1 + s2;
	cout << "3)" << s3 << endl;
	cout << "4)" << s3.size() << endl;
	string s4 = s3.substr(1, 3);//求s3从下标1开始,长度为3的子串
	cout << "5)" << s4 << endl;
	char str[20];
	strcpy(str, s4.c_str());//复制s4中的字符串到str
	cout << "6)" << str << endl;
	return 0;
}

打印输出:
1)abcdef
2)s2<s1
3)XYZ1A31A3
4)9
5)YZ1
6)YZ1

第二章,类和对象

面向对象的程序设计有抽象、封装、继承、多态四个基本特点,多态是指不同种类的对象都具有名称相同的行为。

类的定义

类的定义, class 类名{ 访问范围说明符: 成员变量1 成员变量2 成员函数1 成员函数2 …}; 类的定义要以";"结束
一个类的成员函数之间可以相互调用;类的成员函数可以重载,也可以设定参数的默认值。以前所学的函数不是任何类的成员函数,可称为全局函数

成员函数的实现可以位于类的定义之外,格式:返回值类型 类名::函数名( ) { 语句组 } 定义对象的基本方法,类名 对象名; 对象名的命名规则和普通变量相同,对象也可以看作“类变量”。
在C++中,一个对象占用的内存空间的大小等于其 成员变量 所占用的内存空间的大小之和。成员函数并非每个对象各自存一份,成员函数和普通函数一样,在内存中只有一份,但它可以作用于不同的对象。

#include<iostream>
using namespace std;
class CRectangle
{
public:
	int w, h;
	void init(int w_, int h_);
	int area();
	int perimeter();
};
void CRectangle::init(int w_, int h_)
{
	w = w_; h = h_;
}
int CRectangle::area()
{
	return w*h;
}
int CRectangle::perimeter()
{
	return 2 * (w + h);
}
int main()
{
	int w, h;
	CRectangle r;
	w = 10;
    h = 5;
	r.init(w, h);
	cout << "It's area is" << r.area() << endl;
	cout << "It's perimeter is" << r.perimeter() << endl;
	cout << sizeof(CRectangle) << endl;
	return 0;
}

打印:
It’s area is50
It’s perimeter is30
8

访问对象的成员

1、 对象名.成员名 例如,Crectangle r1 , r2 ; r1.w=5; r2.init(5,4);
2、 指针->成员名 例如,Crectangle r1 , r2 ; Crectangle * p1=&r1; Crectangle * p2=&r2; p1->w=5; p2->init(5,4);
3、 引用名.成员名 例如,Crectangle r2 ; Crectangle & rr=r2 ; rr.w = 5 ; rr.init(5,4) ; //rr的值改变,r2的值也改变

struct类, 将class关键字换成struct
没有成员函数的struct称作结构,结构变量不是对象,有成员函数的struct就是类。 对struct类来说,如果没有访问范围说明符,成员默认地被认为是共有成员。

类成员的可访问范围

private:用来指定私有成员。一个类的私有成员,不论是成员变量还是成员函数,都只能在该类的成员函数内部才能被访问。
public:用来指定共有成。一个类的共有成员在任何地方都可以被访问。
protected:用来指定保护成员。

构造函数 constructor

用于对对象进行自动初始化, 其名字和类的名字一样,不写返回值,可以重载,即一个类可以有多个构造函数。构造函数执行时,对象的内存空间已经分配好了,构造函数的作用是初始化这片空间。
示例,构造函数在数组中的使用:

#include<iostream>
using namespace std;
class CSample
{
public:
	CSample()
	{
		cout << "Constructor 1 Called" << endl;
	}
	CSample(int n)
	{
		cout << "Constructor 2 Called" << endl;
	}
};
int main()
{
	CSample array1[2];
	cout << "step1" << endl;
	CSample array2[2] = { 4,5 };
	cout << "step2" << endl;
	CSample array3[2] = { 3 };
	cout << "step3" << endl;
	CSample* array4 = new CSample[2];
	delete[] array4;
	return 0;
}

打印:
Constructor 1 Called
Constructor 1 Called
step1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called

默认构造函数 default constructor

如果类的设计者没有写构造函数,编译器会自动生成一个没有参数的构造函数,该无参构造函数什么都不做。无参构造函数,不论是编译器自动生成的还是程序员写的,都成为默认构造函数。

复制构造函数

是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。可以const引用,也可以是非const引用,一般使用前者。
定义,Complex( const Complex & c) { } Complex c2 (c1) ; //调用复制构造函数
示例:非默认复制构造函数

#include<iostream>
using namespace std;
class Complex
{
public:
	double real, imag;
	Complex(double r, double i)
	{
		real = r; imag = i;
	}
	Complex(const Complex & c)//定义复制构造函数
	{
		real = c.real; imag = c.imag;
		cout << "Copy Constructor called" << endl;
	}
};
int main()
{
	Complex c1(1, 2);
	Complex c2(c1);//调用复制构造函数
	cout << c2.real << " " << c2.imag;
	return 0;
}

打印
Copy Constructor called
1 2

默认复制构造函数

如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数,称为默认复制构造函数。其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等

复制构造函数被调用的三种情况
1、当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用
例如,Complex c2 (c1) ; Complex c2=c1 ; //第二条语句是初始化语句,不是赋值语句。赋值语句的等号左边是一个已定义的变量,赋值语句不会引发复制构造函数调用,例如 Complex c1,c2 ; c1=c2; c1早已生成,已经初始化了,不调用复制构造函数
2、作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。例如,void Func (A a) { }
如果函数F的参数是类A的对象,那么当F被调用时,类A的复制构造函数将被调用。对象作为函数的形参,要用复制构造函数初始化,这会带来时间上的开销,如果用对象的引用而不是对象作为形参,就没有这个问题了,可用const引用。
3、作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数时的实参,就是return语句所返回的对象。如果函数的返回值是类A的对象,则函数返回时,类A的复制构造函数被调用
例如,A Func( ) { A a(4) ; return a; } //调用Func函数时,其返回值是一个对象,该对象就是用复制构造函数初始化的,而且调用复制构造函数时,实参就是return语句所返回的a。
示例:复制构造函数用于函数形参

#include<iostream>
using namespace std;
class A
{
public:
	A() {};
	A(A &a)
	{
		cout << "Copy constructor called" << endl;
	}
};
void Func(A a)
{

}
int main()
{
	A a;
	Func(a);
	return 0;
}

打印
Copy constructor called

示例:复制构造函数用于函数返回值

#include<iostream>
using namespace std;
class A
{
public:
	int v;
	A(int n)
	{
		v = n;
	}
	A(const A &a)
	{
		v = a.v;
		cout << "Copy constructor called" << endl;
	}
};
A a(4);//不同的编译器会有差异,这里使用mgw640编译器,只有把a定义为全局变量才会调用复制构造函数
A Func()
{
	//A a(4);//vs编译器时,定义为局部变量也会调用复制构造函数
	return a;
}
int main()
{
	cout << Func().v << endl;
	return 0;
}

打印
Copy constructor called
4

类型转换构造函数

除复制构造函数外,只有一个参数的构造函数一般都可以称作类型转换构造函数,因为这样的构造函数能起到类型自动转换的作用。
例如,定义,Complex ( int i) { } 调用,Complex c1(7,8) ; Complex c2=12 ; c1=9; 后面两条语句均调用类型转换构造函数,最后一条赋值语句等号两边的类型不匹配,因为Complex(int)这个类型转换构造函数能接受一个整型参数
编译器在处理c1=9; 这条赋值语句时,会在等号右边自动生成一个临时的Complex对象,该临时对象以9为实参,用Complex(int)构造函数初始化,然后再将这个临时对象赋值给c1,也就是说9被自动转换成一个Complex对象再赋值给c1.
示例:类型转换构造函数

#include<iostream>
using namespace std;
class Complex {
public:
	double real, imag;
	Complex(int i)//类型转换构造函数
	{
		cout << "IntConstructor called" << endl;
		real = i; imag = 0;
	}
	Complex(double r, double i)
	{
		real = r; imag = i;
	}
};
int main()
{
	Complex c1(7, 8);
	Complex c2 = 12;
	c1 = 9;//9被自动转换成一个临时Complex 对象
	cout << c1.real << "," << c1.imag << endl;
	return 0;
}

打印
IntConstructor called
IntConstructor called
9,0

析构函数 destructor

析构函数是成员函数的一种,它的名字与类名相同,但前面要加“~”,没有参数和返回值。 一个类有且仅有一个析构函数,如果定义类时没写析构函数,则编译器生成默认析构函数。
析构函数在对象消亡时自动被调用,可以定义析构函数在对象消亡前做善后工作。函数的参数对象以及作为函数返回值的对象,在消亡时也会引发析构函数的调用。例如,class String{public: String (int n) ; ~String(); }

#include<iostream>
using namespace std;
class CDemo {
public:
	~CDemo()
	{
		cout << "Destructor called" << endl;
	}
};
int main()
{
	CDemo array[2];//构造函数调用2次
	CDemo * pTest = new CDemo;//构造函数调用1次
	delete pTest;//析构函数调用
	cout << "**********" << endl;
	pTest = new CDemo[2];//构造函数调用2次
	delete[] pTest;//析构函数调用2次
	cout << "Main ends" << endl;
	return 0;
}

打印
Destructor called


Destructor called
Destructor called
Main ends
Destructor called
Destructor called

静态成员变量和静态成员函数

在成员变量定义和成员函数声明前面加了 static关键字。例如,class Crectangle { private: int w,h; static int nTotalArea; //静态成员变量 public: static void PrintTotal ( ) ; //定义成员函数 }
普通成员变量每个对象有各自的一份,而静态成员变量只有一份,被所有同类对象共享。普通成员函数一定是作用在某个对象上,而静态成员函数并不具体作用在某个对象上。访问静态成员,“类名::成员名”。
静态成员变量本质上是全局变量,哪怕一个对象都不存在,其静态成员变量也存在。使用sizeof运算符计算对象所占用的存储空间时,不会将静态成员变量计算在内。
注意,静态成员变量必须在类定义的外面专门声明,声明时变量名前面加“类名::”。因为静态成员函数不具体作用于某个对象,静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。

#include<iostream>
using namespace std;
class CRectangle {
private:
	int w, h;
	static int totalArea;//总面积
	static int totalNumber;//总数量
public:
	CRectangle(int w_, int h_);
	~CRectangle();
	static void PrintTotal();
};
CRectangle::CRectangle(int w_, int h_)
{
	w = w_; h = h_;
	totalNumber++;
	totalArea += w*h;
}
CRectangle::~CRectangle()
{
	totalNumber--;
	totalArea -= w*h;
}
void CRectangle::PrintTotal()
{
	cout << totalNumber << "," << totalArea << endl;
}
//必须在定义类的文件中对静态成员变量进行一次声明
//或初始化,否则编译能通过,链接不能通过
int CRectangle::totalNumber = 0;
int CRectangle::totalArea = 0;
int main()
{
	CRectangle r1(3, 3), r2(2, 2);
	CRectangle::PrintTotal();
	r1.PrintTotal();
	return 0;
}

打印
2,13
2,13

常量对象和常量成员函数

定义对象时在前面加const关键字,使之成为常量对象。不能通过常量对象调用普通成员函数,但可以通过常量对象调用常量成员函数。所谓常量成员函数,就是在定义和声明时加了const关键字的成员函数。
例如,class Sample { public:void GetValue( ) const ; }; void Sample :: GetValue( ) const { } int main( ) { const Sample o ; o.GetValue( ) ; return 0; } //常量对象上可执行常量成员函数

#include<iostream>
using namespace std;
class Sample {
public:
	void GetValue() const;
};
void Sample::GetValue()const
{
}
int main()
{
	const Sample o;
	o.GetValue();//常量对象上可以执行常量成员函数
	return 0;
}

成员对象和封闭类

一个类的成员变量如果是另一个类的对象,就称之为成员对象。包含成员对象的类叫封闭类(enclosed class)。

封闭类构造函数的初始化列表
当封闭类的对象生成并初始化时,它包含的成员对象也需要被初始化,这就会引发成员对象构造函数的调用。
类名::构造函数名(参数表):成员变量1(参数表),成员变量2(参数表), { } 初始化列表中的成员变量既可以是成员对象,也可以是基本类型的成员变量。对于成员变量,参数表中存放的是构造函数的参数。
对于基本类型成员变量,参数表中就是一个初始值。例如,class Ctyre { int radius,width; public: Ctyre( int r, int w) : radius( r ),width ( w ){ } //成员变量初始化 };
class Ccar { int price; Ctyre tyre ; public: Ccar (int p,int tr , int tw) } ; Ccar :: Ccar (int p ,int tr , int tw ) :price ( p ) , tyre ( tr,tw) {};
示例

#include<iostream>
using namespace std;
class CTyre
{
private:
	int radius;
	int width;
public:
	CTyre (int r,int w):radius(r),width(w){}//成员变量初始化
};
class CEngine
{};
class CCar {
private:
	int price;
	CTyre tyre;
	CEngine engine;
public:
	CCar(int p, int tr, int tw);
};
CCar::CCar(int p, int tr, int tw) :price(p), tyre(tr, tw)
{};
int main()
{
	CCar car(20000, 17, 225);
	return 0;
}

封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数;成员对象构造函数的执行次序和成员对象在类定义中的次序一致。
当封闭类对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,成员对象析构函数的执行次序和构造函数的执行次序相反,即先构造的后析构,这也是C++处理此类次序问题的一般规律。

封闭类的复制构造函数

封闭类的对象,如果是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。

#include<iostream>
using namespace std;
class A
{
public:
	A() { cout << "default" << endl; }
	A(A & a) { cout << "copy" << endl; }
};
class B
{
	A a;
};
int main()
{
	B b1, b2(b1);
	return 0;
}

打印
default
copy

类的const成员和引用成员

类还可以有常量成员变量和引用型成员变量。这两种类型的成员变量必须在构造函数的初始化列表中进行初始化。常量型成员变量的值一旦初始化就不能再改变。

#include<iostream>
using namespace std;
int f;
class CDemo {
private:
	const int num;
	int & ref;
	int value;
public:
	CDemo(int n):num(n),ref(f),value(4){}
};
int main()
{
	cout << sizeof(CDemo) << endl;
	return 0;
}

打印
12

友元函数

在定义一个类的时候,可以把一些函数(包括全局函数和其他类的成员函数)声明为友元,这些函数就成为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员了。
将全局函数声明为友元: friend 返回值类型 函数名(参数表);
将其他类的成员函数声明为友元: friend 返回值类型 其他类的类名::成员函数名(参数表);

友元类

一个类A可以将另一个类B声明为自己的友元,类B的所有成员函数就都可以访问类A对象的私有成员。 写法,friend class 类名;

this指针

非静态成员函数实际上的形参个数比程序员写的多一个“this指针”,这个this指针指向了成员函数作用的对象。例如,this指针的类型是Complex * ; *this 代表函数所作用的对象。

第三章,运算符重载

运算符重载的实质是编写以运算符作为名称的函数。目的是使得C++中的运算符也能用来操作对象。 返回值类型 operator 运算符(形参表) { }
包含被重载的运算的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。
例如,Complex operator + ( const Complex &a , const Complex &b ) { return Complex (a.real+b.real , a.imag+b.imag) ; //返回一个临时对象 } c=a+b;就等价于c=operator+(a,b)

+和-运算符重载

#include<iostream>
using namespace std;
class Complex
{
public:
	double real, imag;
	Complex(double r = 0.0,double i=0.0):real(r),imag(i){}
	Complex operator -(const Complex & c);
};
Complex operator +(const Complex & a, const Complex & b)
{
	return Complex(a.real + b.real, a.imag + b.imag);
}
Complex Complex::operator-(const Complex &c)
{
	return Complex(real - c.real, imag - c.imag);
}
int main()
{
	Complex a(4, 4), b(1, 1), c;
	c = a + b;//等价于c=operator + (a,b);
	cout << c.real << "," << c.imag << endl;
	cout << (a - b).real << "," << (a - b).imag << endl;//a-b等价于a.operator - (b)
	return 0;
}

=运算符重载

#include<iostream>
#include<string>
using namespace std;
class String
{
private:
	char *str;
public:
	String():str(NULL){}
	const char *c_str()const { return str; };
	String & operator=(const char *s);
	~String();
};
String & String::operator=(const char *s)
{
	if (str)
		delete[]str;
	if (s)
	{
		str = new char[strlen(s) + 1];
		strcpy(str, s);
	}
	else
		str = NULL;
	return *this;
}
String ::~String()
{
	if (str)
		delete[]str;
}
int main()
{
	String s;
	s = "Good luck!";//等价于s.operator=("Good Luck,");
	cout << s.c_str() << endl;
	s = "Shenzhou!";
	cout << s.c_str() << endl;
	return 0;
}

类型强制转换运算符重载

#include<iostream>
using namespace std;
class Complex {
	double real, imag;
public:
	Complex(double r = 0, double i = 0) :real(r), imag(i) {};
	operator double() { return real; }//重载类型强制转换运算符double
};
int main()
{
	Complex c(1.2, 3.4);
	cout << (double)c << endl;
	double n = 2 + c;//等价于double n=2+c.operator double()
	cout << n;
	return 0;
}

打印
1.2
3.2

++ 和 – 运算符重载

#include<iostream>
using namespace std;
class CDemo {
private:
	int n;
public:
	CDemo(int i=0):n(i){}
	CDemo & operator++();
	CDemo operator++(int);
	operator int() { return n; }
	friend CDemo & operator--(CDemo &);
	friend CDemo operator--(CDemo &, int);
};
CDemo & CDemo::operator++()
{
	n++;
	return *this;
}
CDemo CDemo::operator++(int k)
{
	CDemo tmp(*this);
	n++;
	return tmp;
}
CDemo & operator--(CDemo &d)
{
	d.n--;
	return d;
}
CDemo operator--(CDemo & d, int)
{
	CDemo tmp(d);
	d.n--;
	return tmp;
}
int main()
{
	CDemo d(5);
	cout << (d++) << ",";//等价于d.operator++(0)
	cout << d << ",";
	cout << (++d) << ",";//等价于d.operator++()
	cout << d << ",";
	return 0;
}

第四章,继承与派生

继承与派生

派生类时提供对基类进行扩充和修改得到的。 基类的所有成员自动成为派生类的成员。 所谓扩充,指的是派生类中可以添加新的成员变量和成员函数。所谓修改,指的是派生类中可以重写从基类继承得到的成员。
在C++中,派生的写法, class 派生类名:继承方式说明符 基类名 { }
继承方式说明符可以是public、private、protected,一般都使用public。派生类的成员函数不能访问基类的私有成员。
在基类和派生类有同名成员的情况下,在派生类的成员函数中访问同名成员,访问的就是派生类的成员,这种情况叫“覆盖”,即派生类的成员覆盖基类的同名成员。如要访问基类的同名成员,需要在成员名前加“基类名::”
在派生类的同名成员函数中,先调用基类的同名成员函数完成基类部分的功能,然后再执行自己的代码完成派生类的功能。
示例:

#include<iostream>
#include<string>
using namespace std;
class CStudent {
private:
	string name;
	string id;
	char gender;
	int age;
public:
	void PrintInfo();
	void SetInfo(const string & name_, const string id_, int age_, char gender_);
	string GetName() { return name; }
};
class CUndergraduateStudent :public CStudent {
private:
	string department;
public:
	void QulifiedForBaoyan() {//保研资格
		cout << "qulified for baoyan" << endl;
	}
	void PrintInfo() {
		CStudent::PrintInfo();//调用基类的PrintInfo
		cout << "Department:" << department << endl;
	}
	void SetInfo(const string & name_, const string id_, int age_, char gender_, const string & department_) {
		CStudent::SetInfo(name_, id_, age_, gender_);//调用基类的SetInfo
		department = department_;
	}
};
void CStudent::PrintInfo() {
	cout << "Name:" << name << endl;
	cout << "Id:" << id << endl;
	cout << "Age:" << age << endl;
	cout << "Gender:" << gender << endl;
}
void CStudent::SetInfo(const string & name_, const string id_, int age_, char gender_) {
	name = name_;
	id = id_;
	age = age_;
	gender = gender_;
}
int main()
{
	CStudent s1;
	CUndergraduateStudent s2;
	s2.SetInfo("Harry Potter", "118829212", 19, 'M', "Computer Science");
	cout << s2.GetName() << " ";
	s2.QulifiedForBaoyan();
	s2.PrintInfo();
	cout << "size of string=" << sizeof(string) << endl;
	cout << "size of CStudent=" << sizeof(CStudent) << endl;
	cout << "size of =CUndergraduateStudent" << sizeof(CUndergraduateStudent) << endl;
	return 0;
}

打印:
Harry Potter qulified for baoyan
Name:Harry Potter
Id:118829212
Age:19
Gender:M
Department:Computer Science
size of string=24
size of CStudent=56
size of =CUndergraduateStudent80

protected访问范围说明符

保护成员的可访问范围比私有成员大,比共有成员小。能访问私有成员的地方都能访问保护成员。保护成员扩大的访问范围:基类的保护成员可以在派生类的成员函数中被访问。

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

派生类对象在创建时,除了要调用自身的构造函数进行初始化外,还要调用基类的构造函数初始化其包含的基类对象。
和封闭类初始化类似,在构造函数后面添加初始化列表。 构造函数名(形参表):基类名(基类构造函数实参表)
在执行派生类的构造函数之前,总是先执行基类的构造函数。 派生类对象消亡时,先执行派生类的析构函数,再执行基类的析构函数。
示例,派生类的构造函数和析构函数调用顺序:

#include<iostream>
#include<string>
using namespace std;
class CBug
{
	int legNum, color;
public:
	CBug(int ln,int cl):legNum(ln),color(cl)
	{
		cout << "CBug Constructor" << endl;
	}
	~CBug()
	{
		cout << "CBug Destructor" << endl;
	}
	void PrintInfo()
	{
		cout << legNum << "," << color << endl;
	}
};
class CFlyingBug :public CBug
{
	int wingNum;
public:
	//CFlyingBug(){}//报错
	/*CFlyingBug::*/CFlyingBug(int ln,int cl,int wn):CBug(ln,cl),wingNum(wn)
	{
		cout << "CFlyingBug Constructor" << endl;
	}
		~CFlyingBug()
	{
		cout << "CFlyingBug Destructor" << endl;
	}
};
int main()
{
	CFlyingBug fb(2, 3, 4);
	fb.PrintInfo();
	return 0;
}

打印:
CBug Constructor
CFlyingBug Constructor
2,3
CFlyingBug Destructor
CBug Destructor

共有派生的赋值兼容规则

1、派生类的对象可以赋值给基类对象
2、派生类对象可以用来初始化基类引用
3、派生类对象的地址可以赋值给基类指针,亦即派生类的指针可以赋值给基类的指针
例如,两哥类的对象a=b; 在赋值号“=” 没有被重载的情况下,所做的操作就是将派生类对象中的基类对象逐个字节地复制到“=”左边的基类对象中。

第五章,多态和虚函数

多态

多态(polymorphism)指的是同一名字的事物可以完成不同的功能。多态可以分为编译时的多态和运行时的多态。
编译时的多态:指函数的重载、对重载函数的调用,在编译时就能根据实参确定应该调用哪个函数。运行时的多态,和继承、虚函数等相关。

通过基类指针实现多态

派生类对象的地址可以赋值给基类指针。通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句,编译时并不确定要执行的是基类还是派生类的虚函数。在程序运行时,看基类指针指向的是基类对象还是派生类对象。
所谓虚函数,就是在声明时前面加了virtual关键字的成员函数
virtual关键字只在类定义中的成员函数声明处使用。静态成员函数不能是虚函数。 包含虚函数的类成为“多态类”。

通过基类引用实现多态

通过基类的引用调用虚函数的语句也是多态的。例如,class A{virtual void Print(){} };class B:public A {virtual void Print(){} }; void PrintInfo(A & r) { r.Print(); } //多态,调用哪个Print,取决于r引用了哪个类的对象 PrintIInfo(a);

#include<iostream>
using namespace std;
class A
{
public:
	virtual void Print() { cout << "A::Print" << endl; }
};
class B :public A
{
public:
	virtual void Print() { cout << "B::print" << endl; }
};
void PrintInfo(A &r)
{
	r.Print();//多态,调用哪个Print取决于r引用了哪个类的对象
}
int main()
{
	A a; B b;
	PrintInfo(a);//输出A::Print
	PrintInfo(b);//输出B::Print
	return 0;
}

多态的实现原理

有了虚函数,对象所占用的存储空间比没有虚函数时多了4个字节,这4个字节位于对象存储空间的最前端,其中存放的是虚函数表的地址。
每一个有虚函数的类都有一个虚函数表,该类的任何对象中都放着该虚函数表的指针。虚函数表中列出了该类的全部虚函数地址。多态的函数调用语句被编译成根据基类指针所指向的对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用。

在成员函数中调用虚函数

类的成员函数之间可以相互调用。在成员函数中调用其他虚成员函数的语句是多态的(静态成员函数、构造函数、析构函数除外)

#include<iostream>
using namespace std;
class CBase
{
public:
	void func1() { func2(); }
	virtual void func2() { cout << "CBase::func2()" << endl; }
};
class CDerived :public CBase
{
public:
	virtual void func2() { cout << "CDerived:func2()" << endl; }
};
int main()
{
	CDerived d;
	d.func1();
	return 0;
}

打印
CDerived:func2()

在构造函数和析构函数中调用虚函数

在构造函数和析构函数中调用虚函数不是多态的。如果本类有该函数,调用的就是本类的函数;如果本类没有,调用的就是直接基类的函数;如果直接基类没有,调用的就是间接基类的函数。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void hello() { cout << "A::hello" << endl; }
	virtual void bye() { cout << "A::bey" << endl; }
};
class B :public A
{
public:
	virtual void hello() { cout << "B::hello" << endl; }
	B() { hello(); }
	~B() { bye(); }
};
class C :public B
{
public:
	virtual void hello() { cout << "C::hello" << endl; }
};
int main()
{
	C obj;
	return 0;
}

打印
B::hello
A::bye

区分多态和非多态情况

通过基类指针或引用调用成员函数的语句,只有当该成员函数时虚函数时才会是多态。 另C++规定,只要基类中的某个函数被声明为虚函数,则派生类中的同名、同参数表的成员函数即使前面不写virtual关键字,也自动成为虚函数。

#include<iostream>
using namespace std;
class A
{
public:
	void func1() { cout << "A::func1" << endl; }
	virtual void func2() { cout << "A::func2" << endl; }
};
class B :public A
{
public:
	virtual void func1() { cout << "B::func1" << endl; }
	void func2() { cout << "B::func2" << endl; }//虚函数
};
class C :public B
{
public:
	void func1() { cout << "C::func1" << endl; }//虚函数
	void func2() { cout << "C::func2" << endl; }//虚函数
};
int main()
{
	C obj;
	A * pa = &obj;
	B * pb = &obj;
	pa->func2();//多态
	pa->func1();//不是多态
	pb->func1();//多态
	return 0;
}

打印
C::func2
A::func1
C::func1

虚析构函数

只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字,都自动成为虚析构函数。一个类如果定义了虚函数,则最好将析构函数也定义成虚函数。
派生类的析构函数会自动调用基类的析构函数。析构函数可以是虚函数,但是构造函数不能是虚函数。

#include<iostream>
using namespace std;
class CShape
{
public:
	virtual ~CShape() { cout << "CShape::destructor" << endl; }
};
class CRectangle :public CShape
{
public:
	int w, h;
	~CRectangle() { cout << "CRectangle::destructor" << endl; }
};
int main()
{
	CShape* p = new CRectangle;
	delete p;
	return 0;
}

打印
CRectangle::destructor
CShape::destructor

纯虚函数和抽象类

纯虚函数就是没有函数体的虚函数。包含纯虚函数的类就叫抽象类。
纯虚函数的写法就是在函数声明后面加“=0”,不写函数体。例如,virtual void Print()=0; 抽象类不能生成独立的对象。
可以定义抽象类的指针或引用,并让它们指向或引用抽象类的派生类的对象,以实现多态。 如果一个类从抽象类派生而来,那么当且仅当它对基类中的所有纯虚函数都进行覆盖并都写出函数体,它才能成为非抽象类。

第六章,附录知识

函数模板

函数模板是用于生成函数的,写法, template <class 类型参数1,class 类型参数2,…> 返回值类型 模板名 (形参表) {函数体}
例如,template void Swap(T & x , T & y ) {T tmp=x; x=y; y=tmp; } //调用 Swap(n,m); 调用时会用具体的类型名对模板中所有的类型参数进行替换,其他部分原封不动的保留。
编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数成为模板函数。

类模板

人们需要编写多个形式和功能都相似的类,于是C++引入类模板的概念,编译器可以从类模板自动生成多个类。
C++中类模板的写法, template<class 参数类型1,class 参数类型2…> class 类模板名{ 成员函数和成员变量 }
模板中的成员函数放到类模板定义外面时的写法,template<类型参数表> 返回值类型 类模板名<类型参数名列表>::成员函数名(参数表) { } 例如,template <class T1,T2> bool Pair<T1,T2>::operator<(const Pair<T1,T2> & P)const {return key<p.key;}
用类模板定义对象的写法, 类模板名<真实类型参数表> 对象名 (构造函数实际参数表);例如,Pair<string,int>student (“Tom” , 19);
如果类模板有无参构造函数,也可以使用如下写法, 类模板名<真实类型参数表> 对象名;

编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类叫模板类。
函数模板和类模板介绍

typename

typename:是一个C++程序设计语言中的关键字,相当用于泛型编程时是另一术语"class"的同义词。这个关键字用于指出模板声明(或定义)中的依赖的名称(dependent names)是类型名,而非变量名
typedef用法,为现有类型创建一个新的名字
1、定义一种类型的别名,而不只是简单的宏替换
char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, 和一个字符变量
typedef char* PCHAR; // 一般用大写
PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
2、旧的C代码声明struct新对象时: struct 结构名 对象名,而C++中可以直接写:结构名 对象名
利用typedef的简化写法:typedef struct tagPOINT {…}POINT; 这样定义结构体对象时就可以写成:POINT p1;
3、用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健

C++类型转换

const_cast:去掉类型的const或volatile属性,去const属性
static_cast:无条件转换,静态类型转换,基本类型转换,类似C的强转Type b = (Type)a
dynamic_cast:有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL),多态类之间的类型转换
reinterpret_cast:仅重新解释类型,但没有进行二进制的转换,不同类型的指针类型转换

C++内存

1.栈,那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等
2.堆,那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收
3.自由存储区,那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量

C++智能指针

unique_ptr,由 C++11 引入,旨在替代不安全的 auto_ptr,它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象,只能移动 unique_ptr,即对资源管理权限可以实现转移
auto_ptr,由 C++98 引入,auto_ptr有赋值语义,赋值后则两个指针将指向同一个 对象。这是不能接受的,因为程序将试图删除同一个对象两次,因此auto_ptr是不安全的
shared_ptr,是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象
weak_ptr,被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来,它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权, weak_ptr 可用于打破循环引用

指针函数

顾名思义,它的本质是一个函数,不过它的返回值是一个指针
ret *func(args, …);
其中,func是一个函数,args是形参列表,ret *作为一个整体,是 func函数的返回值,是一个指针的形式。

函数指针

函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针
我们知道,函数的定义是存在于代码段,因此,每个函数在代码段中,也有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针
ret (*p)(args, …);
其中,ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量 。
举例:
double cal(int); // prototype
double (*pf)(int); // 指针pf指向的函数, 输入参数为int,返回值为double
pf = cal; // 指针赋值
void estimate(int lines, double (*pf)(int)); // 函数指针作为参数传递,函数名就是指针,调用时直接传入函数名:estimate(line_num, cal_m1);
double y = cal(5); // 通过函数调用
double y = (*pf)(5); // 通过指针调用 推荐的写法

函数对象(仿函数)

函数对象:定义了调用操作符()的类对象。当用该对象调用此操作符时,其表现形式如同普通函数调用一般,因此取名叫函数对象。

    class A 
    {
    public:
        int operator() ( int val ) 
        {
            return val > 0 ? val : -val;
        }
    };

调用:
int i = -1;
A func;
cout << func(i);

c++ 函数前面和后面 使用const 的作用

前面使用const 表示返回值为const
后面加 const表示函数不可以修改class的成员

C++标准模板库(Standard Template Library)
STL中的几个基本概念
容器(container):用于存放数据的类模板。可变长数组、链表、平衡二叉树等数据结构在STL中都被实现为容器。 [kənˈtenɚ]
迭代器(iterator):用于存取容器中存放的元素的工具,迭代器是一个变量,作用类似于指针。 [ɪtə’reɪtə]
算法(algorithm):用来操作容器中元素的函数模板。 [ˈælɡəˌrɪðəm]

第七章,C++标准模板库STL

容器

容器中可以存放基本类型的变量,也可以存放对象。 对象或变量插入容器时,实际插入的是对象或变量的一个复制品。 被存入容器的对象所属的类最好重载“==”和“<”运算符。

顺序容器
顺序容器有一下三种:可变长数组vector、双端队列deque、双向链表list
之所以成为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。

关联容器
关联容器有以下四种:set、multiset、map、multimap
关联容器内的元素是排序的,插入元素时,容器会按一定的排序规则将元素放在适当的位置上。默认情况下,关联容器中的元素是从小到大排序的。 因为排序,关联容器在查找时具有非常好的性能。
set,排好序的集合,不允许有相同的元素
multiset,排好序的集合,允许有相同元素
map,每个元素都分为关键字和值两个部分,容器中的元素是按关键字排序的,不允许有多个元素的关键字相同
multiset,和map类似,区别在于元素的关键字可以相同

不能修改关联容器中元素的值,因为元素修改后容器并不会自动重新排序。正确的做法的是, 先删除该元素,再插入新元素。

容器适配器
STL在两类容器的基础上屏蔽一部分功能,突出或增加另一部分功能,实现了三种容器适配器:栈stack、队列queue、优先级队列priority_queue
STL中容器适配器有stack、queue、priority_queue三种,它们都是在顺序容器的基础上实现的。
容器适配器都有以下三个成员函数:
push:添加一个元素
top:返回顶部或对头的元素的引用
pop:删除一个元素
容器适配器是没有迭代器的,因此STL中的各种排序、查找、变序等算法都不适用于容器适配器。

容器都是类模板,它们实例化后就成为容器类。用容器类定义的对象成为容器对象。 例如vector是一个容器类的名字,vectora; 就定义了一个容器对象a。
任何两个容器对象,只要它们的类型相同,就可以用<、<=、>、>=、、!=进行词典式的比较运算
a
b:若a和b中的元素个数相同,且对应元素均相等,则“a==b”的值为true
a<b:规则类似于词典中两个单词的比较大小,从头到尾依次比较每个元素,如果发生a中的元素小于b中的元素的情况,则a<b的值为true;如果没有发生b中的元素小于a中的元素的情况,且a中的元素个数比b少,则a<b的值也为true

所有容器都有以下两个成员函数
int size( ) 返回容器对象中元素的个数
bool empty( ) 判断容器对象是否为空

顺序容器和关联容器还有以下成员函数
begin( ) 返回指向容器中第一个元素的迭代器
end( ) 返回指向容器中最后一个元素后面的位置的迭代器
rbegin( ) 返回指向容器中最后一个元素的反向迭代器
rend( ) 返回指向容器中第一个元素前面的位置的反响迭代器
erase( ) 从容器中删除一个或几个元素
clear( ) 从容器中删除所有的元素
如果一个容器为空,则begin()和end()的返回值相等,rbegin()和rend()的返回值也相等

顺序容器还有以下常用成员函数
front() 返回容器中第一个元素的引用
back() 返回容器中最后一个元素的引用
push_back() 在容器末尾增加新元素
pop_back() 删除容器末尾的元素
insert() 插入一个或多个元素

迭代器

迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。迭代器和指针类似
1、正向迭代器, 容器类名::iterator 迭代器名;
2、常量正向迭代器,容器类名::const_iterator 迭代器名;
3、反响迭代器, 容器类名::reverse_iterator 迭代器名;
4、常量反响迭代器, 容器类名::const_reverse_iterator 迭代器名;

迭代器用法
*迭代器名 就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素
迭代器都可以进行++操作
反向迭代器和正向迭代器的区别:对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素
后置++在重载时要多生成一个局部对象tmp,因此运行速度比前置++慢。对循环变量i,要养成写++i,不写i++的习惯。

注意容器适配器stack、queue、priority_queue没有迭代器,容器适配器有一些成员函数可以用来对元素进行访问。

迭代器功能分类
1、正向迭代器,支持以下操作,++p、p++、*p。 此外,两个正向迭代器可以相互赋值,还可以用==和!= 运算符进行比较。
2、双向迭代器,支持正向迭代器的全部功能,此外还支持–p和p–操作,–p使得p朝和++p相反的方向移动
3、随机访问迭代器,支持双向迭代器的全部功能,若p是个随机访问迭代器,i是一个整型变量或常量,则p还支持以下操作:
p+=i:使得p往后移动i个元素
p-=i:使得p往前移动i个元素
p+i:返回p后面第i个元素的迭代器
p-i:返回p前面第i个元素的迭代器
p[i]:返回p后面第i个元素的引用
以外,两个随机访问迭代器p1、p2还可以用<、>、<=、>=运算符进行比较,p1<p2的含义是,p1经过若干次++操作后,就会等于p2。 p2-p1也是有定义的,其返回值是p2所指向的元素和p1所指向的元素的序号只差。

在C++中,数组也是容器,数组的迭代器就是指针,而且是随机访问迭代器。
在这里插入图片描述
迭代器的辅助函数
1、advance(p,n),使迭代器p向前或向后移动n个元素
2、distance(p,q),计算两个迭代器之间的距离,即迭代器p经过多少次++操作后和迭代器q相等。如果调用时p已经指向q的后面,则会陷入死循环。
3、iter_swap(p,q),用于交换两个迭代器p、q指向的值
要使用这些函数模板,需要保护头文件algorithm

算法

算法就是函数模板,STL中的算法大多数是用来对容器进行操作的。大部分算法定义在头文件algorithm中,还有一些算法用于数值处理,定义在头文件numeric中。
算法大致分为七类:
1、不变序列算法
2、变值算法
3、删除算法
4、变序算法
5、排序算法
6、有序区间算法
7、数值算法
算法是一些容器函数操作,这里没有详述。

STL中大、小、相等的概念
默认情况下,比较大小是通过< 运算符进行的,和> 运算符无关。
x和y相等的意义
在未排序的区间上进行的算法,如顺序查找算法find,查找过程中比较两个元素是否相等用的是运算符;
但是对于在排好序的区间上进行查找、合并等操作的算法来说,x和y相等 是与 “x<y和y<x同时为假”等价,与
运算符无关。

string

初始化
string s1 = “aaaa”; //通过const char * 初始化
string s2(“bbbbb”); //构造函数初始化
string s3 = s2; //通过拷贝构造函数来初始化对象s3
string s4(10, ‘a’); //用10个’a’字符来初始化字符串
string strtmp(str, 0, 8); //取下标0开始,8个长度的字符赋值给strtmp
str.push_back(‘5’); 尾部加一个字符,如果参数是int型,会转换为ASCII码

字符串的遍历 string str(“abcdefg”);
str[i] 数组方式遍历,通过[]操作符遍历 (不会抛出异常)
str.at(i) at()方法遍历,根据index取值 (会抛出异常)
*it 通过STL迭代器遍历

string转char * :string提供了成员函数c_str来将string对象转化成const char *
string str(“aaaaaa”);
const char *p = str.c_str();

char *转string
const char *p1 = “12345”;
string str2 = p1;

string的拼接
string s3 = s1 + s2; //直接使用加号运算符拼接
string s4 = s1.append(s2); //使用成员函数append()拼接

string的查找和替换
size_t index1 = s1.find(“hello”,0); //从0位置开始查找第一个hello出现的首位位置
size_t index2 = s1.find_first_of(“hello”);//查找第一个hello出现时的首位位置
size_t index3 = s1.find_last_of(“hello”); //查找最后一个hello出现时的末尾位置
这些操作全都返回 string::size_type 类型的值,以下标形式标记查找匹配所发生的位置;或者返回一个名为 string::npos 的特殊值,说明查找没有匹配;string 类将 npos 定义为保证大于任何有效下标的值。

s1.replace(offindex1, strlen(“hello”), “welcome”); //先删除指定位置,指定长度的字符,然后在当前指定位置插入新的字符

string区间删除和插入
string &insert(int pos, const char *s); //pos位置插入字符串s,返回新的string
string &insert(int pos, const string &s); //pos位置插入字符串s,返回新的string
string &insert(int pos, int n, char c); //pos位置插入n个字符c,返回string
string &erase(int pos, int n); //删除从pos位置开始的n个字符,返回新的string
string::iterator erase(string::iterator it); //删除指定迭代器的位置,返回当前迭代器位置
string::iterator erase(string::iterator beginIt, string::iterator endIt); //删除迭代器之间的字符,左闭右开区间

transform:将某操作应用于指定范围的每个元素,transform函数有两个重载版本:
transform(first,last,result,op);//first是容器的首迭代器,last为容器的末迭代器,result为存放结果的容器,op为要进行操作的一元函数对象或sturct、class。
transform(first1,last1,first2,result,binary_op);//first1是第一个容器的首迭代 器,last1为第一个容器的末迭代器,first2为第二个容器的首迭代器,result为存放结果的容器,binary_op为要进行操作的二元函数 对象或sturct、class

vector

vector是将元素放到动态数组中加以管理的容器。vector容器可以随机存取元素,也就是说支持[]运算符和at方式存取
vector在尾部添加或者移除元素非常快,在中间操作非常耗时,因为它需要移动元素

front()返回头部元素的引用,可以当左值
back()返回尾部元素的引用,可以当左值
push_back()添加元素,只能尾部添加
pop_back()移除元素,只能在尾部移除

vector的初始化
v1.push_back(1); //直接构造函数初始化
vector v2 = v1; //通过拷贝构造函数初始化
vector v4(v1.begin(), v1.end()); //使用部分元素来构造
vector v5(3,9); //存放三个元素,每个元素都是9,第二个参数不填默认每个元素都是0

vector的遍历
[]方式,如果越界或出现其他错误,不会抛出异常,可能会崩溃,可能数据随机出现
at方式,如果越界或出现其他错误,会抛出异常,需要捕获异常并处理
迭代器方式,正向迭代器遍历或逆向迭代器遍历
(vector::iterator it = v1.begin(); it != v1.end(); it++) //正向迭代器
(vector::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) //逆向迭代器

vector的元素删除
vector的删除,是根据位置进行删除,如果想要删除某个元素,需要找到当前元素的迭代器位置,再进行删除。
erase(iterator)函数,删除后会返回当前迭代器的下一个位置。
v1.erase(v1.begin(), v1.begin() + 3); //区间删除, 删除前3个元素
v1.erase(v1.begin() +3); //删除指定位置的元素

vector的插入元素
vector提供了insert函数,结合迭代器位置插入指定的元素。
v1.insert(v1.begin() + 3, 10); //在指定的位置插入元素10的拷贝
v1.insert(v1.begin(), 3, 11);//在指定的位置插入3个元素11的拷贝

vector遍历

// vector容器遍历方式1 —— 下标遍历
void traverseVector_1(vector<int> v)
{
	for(unsigned int i = 0; i < v.size(); ++i)
	{
		cout<<v[i]<<" ";
	}
	cout<<endl;
}

// vector容器遍历方式2 —— 迭代器遍历
void traverseVector_2(vector<int> v)
{
	// 注:如果参数为const vector<int> 需要用const_iterator
	vector<int>::iterator it = v.begin();
	// vector<int>::const_iterator iter=v.begin();
	for(; it != v.end(); ++it)
	{
		cout<<(*it)<<" ";
	}
	cout<<endl;
}

reserve和resize
reserve,只分配空间,不创建对象,不可访问,用于大量成员创建,一次性分配空间,push_back时就不用再分配,提高执行效率。
resize,分配空间的同时还创建对象,并给对象赋初始值,可访问。

deque

deque是一个双端数组容器:可以在头部和尾部操作元素

push_back 从尾部插入元素
push_front 从头部插入元素
pop_back 从尾部删除元素
pop_front 从头部删除元素

distance函数可以求出当前的迭代器指针it距离头部的位置,也就是容器的指针
用法: distance(v1.begin(), it) ,可以取下标

stack

在数据结构中,栈是一种先入后出的容器,增加元素叫压栈或者入栈。移除元素通常叫做出栈
s1.push(1); //入栈
s1.top(); //当前栈顶元素
s1.pop(); //出栈
stack
要使用stack,必须包含头文件stack。Stack就是栈,栈是一种后进先出的元素序列,访问和删除都只能对栈顶的元素进行。栈内元素不能访问。
stack三个成员函数
void pop(); 弹出(即删除)栈顶元素
T & top(); 返回栈顶元素的引用,通过此函数可以读取栈顶元素的值,也可以修改栈顶元素
void push(const T & x);将x压入栈顶

queue

队列是一种数据结构,具备队头和队尾。常见的有FIFO(先入先出)队列
q.push(1);
q.front(); //队头元素
q.pop(); //弹出队头

要使用queue,必须包含头文件queue。Queue就是队列,是先进先出的。队头的访问和删除操作只能在队头进行,添加操作只能在队尾进行。不能访问队列中间的元素。
queue同样可以使用和stack类似的push、pop、top函数。区别在于,queue的push发生在队尾,pop和top发生在队头。

list

可以在头部和尾部插入和删除元素 ;不能随机访问元素,也就是迭代器只能++,不能一次性跳转,不能随机访问
l.push_back(i); //尾部添加元素
l.push_front(111); //头部添加元素

list的删除
erase是通过位置或者区间来删除,主要结合迭代器指针来操作
remove是通过值来删除
l.erase(l.begin()); //删除头部元素
l.erase(l.begin(), it); //删除某个区间
l.remove(100); //移除值为100的所有元素

priority_queue

STL中的priority_queue优先级队列容器
优先级队列分为:最小值优先队列和最大值优先队列。此处的最大值、最小值是指队头的元素(增序、降序)。默认,是创建最大值优先级队列。
priority_queue默认定义int类型的最大值队列
priority_queue<int, vector, less>定义int型的最大值优先队列
priority_queue<int, vector, greater>定义int型的最小值队列
less和greater相当于谓词,是预定义好的排序函数,我们称之为“仿函数”。

使用priority_queue,必须包含头文件queu。priority_queue是优先队列。它和普通队列的区别在于,优先队列的队头元素总是最大的,即执行pop操作时,删除的总是最大的元素;执行top时返回的是最大值的引用。

set

C++的set容器,其中包含的元素是唯一的,而且是有序的;默认的顺序是从小到大
C++的set容器,是按照顺序插入的,不能在指定位置插入。
C++的set容器,其结构是红黑二叉树,插入数据的效率比vector快

set元素的插入和删除
set提供了insert和erase函数,用来对元素进行插入和删除操作
基础类型数据,如果插入的是重复的元素,则插入失败,返回值是一个pair类型;pair类型类似于swift语言中的元组的概念,通过其判断是否插入成功
复杂类型数据,需要通过仿函数来确定元素的顺序,进入判断是否是重复元素。在“自定义对象的排序”里面讲解

普通数据类型的排序
set创建默认的从小到大的int类型的集合
set<int,less>创建一个从小打到大的int类型的集合
set<int,greater>创建一个从大到小的int类型的集合
上面的less和greater就是仿函数,集合会根据这个仿函数的返回值是否为真类进行排序

自定义对象的排序
//提供仿函数,用于自定义对象的set进行排序,要写一个仿函数,用来排序

struct FuncStudent
{
    //重载了括号操作符,用来比较大小
    bool operator() (const Student &left, const Student &right)
    {
        //如果左边比右边小,从小到大按照年龄排序
        if(left.age < right.age)
            return true;
        else
            return false;
    }
};

pair类型的返回值
这个类型包含了多个数据类型,在函数返回的时候,可以同时返回多个值
pair类型的定义它实际上是一个结构体。它包含了两个属性,first和second。

我们知道set集合中的元素是唯一的,重复的元素插入会失败。如果判断是否插入成功,我们可以通过insert函数的返回值来判断,它的返回值是一个pair类型。我们来看一下insert函数的原型:
pair<iterator,bool> insert(const value_type& __v)
返回的是pair<iterator, bool>类型,pair的第一个属性表示当前插入的迭代器的位置,第二个属性表示插入是否成功的bool值。所以,我们可以通过第二个属性来判断元素是否插入成功。

pair<set<Student, FuncStudent>::iterator, bool> pair1 = set1.insert(s1); //用例,插入数据,接收返回值

set容器数据的查找
iterator find(const key_type& __k) find函数查找元素为k的迭代器位置
iterator lower_bound(const key_type& __k) lower_bound函数查找小于等于元素k的迭代器位置
iterator upper_bound(const key_type& __k) upper_bound函数查找大于元素k的迭代器位置
pair<iterator,iterator> equal_range(const key_type& __k) equal_range函数返回一个pair类型,第一个属性表示大于等于k的迭代器位置,第二个是大于k的迭代器位置

multiset

multiset容器,与set容器相似,但是multiset容器中的元素可以重复。另外,他也是自动排序的,容器内部的值不能随便修改,因为有顺序的。

map

map和multimap是一个键值映射的容器。map中的键值对都是唯一的,但是multimap中一个键可以对应多个值。
map和multimap是关联式容器,键值成对存在
map和multimap是红黑变体的平衡二叉树结构
map只支持唯一的键值对,集合中的元素是按照一定的顺序排列的
multimap中的键可以出现多次
map和multimap的元素插入过程是按照顺序插入的

map元素的插入

//insert方法插入
    //--1 通过pair<int, string>(1,”chenhua“) 构造pair元素
    map1.insert(pair<int, string>(1,"chenhua"));
    //--2 通过make_pair构造pair元素
    map1.insert(make_pair(2,"mengna"));
    //--3 通过value_type构造pair元素
    map1.insert(map<int, string>::value_type(3,"chenmeng"));
//[ ]直接插入
    map1[4] = "menghua";

map的insert函数返回的是pair类型,pair的第二个参数表示是否插入成功。如果插入的元素键名相同,则插入失败。

//重复插入(插入会不成功)
    pair<map<int, string>::iterator, bool> pair1 = map1.insert(make_pair(2, "haha"));
    if (pair1.second) {
        cout << "重复插入成功" << endl;
    }else{
        cout << "重复插入失败" << endl;
    }
//元素的修改
    //map[1] = "22"的方式,如果不存在键则插入,存在键则修改
    map1[2] = "haha";
//元素的删除
    //--删除值为"haha"的元素
    for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
        if (it->second.compare("haha") == 0) {
            map1.erase(it);
        }
    }
//遍历
    for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
        cout << it->first << "\t" << it->second << endl;
    }

map元素的查找
map提供了两个函数进行key的查找:find和equal_range。
//查找key=100的键值对,返回key=100的迭代器位置
map<int, string>::iterator it = map1.find(100);
/查找key = 1000的位置
//返回两个迭代器,第一个表示<=1000的迭代器位置,第二个是>1000的迭代器位置
pair<map<int, string>::iterator, map<int, string>::iterator> mypair = map1.equal_range(1000);

map迭代器安全删除元素

#include<iostream>
    mmp.insert(std::make_pair(2,"22"));
    mmp.insert(std::make_pair(1,"11"));
    mmp.insert(std::make_pair(3,"33"));

    std::map<int,std::string>::iterator ite = mmp.begin();
    while(ite!=mmp.end())
    {
        if(ite->first == 1)
        {
            ite = mmp.erase(ite);
        }
        else
            ++ite;
    }

    for (std::map<int,std::string>::iterator it = mmp.begin(); it != mmp.end(); it++)
    {
        std::cout << it->first << "\t" << it->second << std::endl;
    }

multimap

multimap容器,与map容器的唯一区别是:multimap支持多个键值
由于支持多个键值,multimap提供了cout函数来计算同一个key的元素个数
//按部门显示员工信息
int developNum = (int) map2.count(“development”);
cout << “development部门人数:” << developNum << endl;

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值