- r引用n,r等价与n(别名)
- r就是n,而不是n的副本,对r修改,n也跟着改变
类型名 & 引用名 = 某变量名
int n = 4;
int& r = n;
// r 引用了 n, r 的类型为 int&
- 可以对函数赋值,??????
- 定义引用时,前面加
const
关键字- 单向的,r引用n后,只能通过r访问n,而不能修改n
const int& r = n
// r 的类型为 : const int&
- const(=constant): In computer programming, a constant is an identifier with an associated value which cannot be altered by the program during normal execution
const int r = 50;
const string r = "O,Fuck"
const int& r = n;
const int* p = & n
- new运算符的返回值类型为:指针(指向分配空间起始地址)
int* p;
p = new int;
sizeof(T)
字节的存储空间,并将该内存空间的起始地址赋值给指针pP = new T[N]
N*sizeof(T)
字节的存储空间,p指向起始地址
- 只能 deletenew出来的空间(p必须指向new出来的空间)
- delete只能做一次,如果对一块new出来的空间delete两次,会导致异常(编译没问题,但可能误删数据)
delete对象 | 用法 | 备注 |
---|---|---|
释放变量空间 | delete p; |
|
释放数组空间 | delete [ ] p; |
p无须指向起始地址,可指数组空间中任意地址 |
- 函数语句少时,调用时间显得大
- 减少函数调用开销
- 跳过函数调用,直接执行函数体(将函数体直接插到调用函数处)
inline int fun(int a, int b){
...
}
- 名字相同,参数表(参数个数,参数类型…)不同的函数之间构成,重载关系
- 缺省值:就是默认值(Default),定义函数时给某些参数提前初始化,如果调用时不写这些参数,就使用缺省值
void fun(int a, int b = 2, int c = 3){
...
}
fun(10); == fun(10, 2, 3);
fun(10, 8); == fun(10, 8, 3);
fun(10, , 8); != fun(10, 2, 3);
- 数据结构(抽象事物的属性/数据) + 函数(抽象事物的行为/方法)
- 类的名字就是一种自定义的类型,(class X ,X就是一个自定义的类型,像 int…一样)
①直接:
对象.成员变量(或函数)
②指针:
先定义“类 类型”的指针指向对象, 再用:指针->成员变量
(或函数)
ClassName object;
ClassName * point = & object;
p -> variable = 5;
p -> reference;
③引用:
先定义“类& 类型”的引用,将对象o“重命名”,再通过引用.成员变量(或函数)
调用
ClassName object;
ClassName & reference = object;
reference.variable= 5;
reference.function(....);
方法:
class ClassName{
...
};
int ClassName::FunName(){
...
}
private
, public
,protected
:关键字 | 作用 | 备注 |
---|---|---|
private | 私有成员,只能在成员函数内被访问 | |
public | 公有成员,可在任何地方被访问 | |
protected | 保护成员,***空空空空*** |
class ClassName{
private:
int a;
public:
int b;
void f1(int c, ...);
...
};
class ClassName{
int a;
...
};
int main(){
ClassName obj;
printf("%d", obj.a); //error
}
class CEmployee{
private:
char szName[30]; //名字
void f();
public:
int salary; //工资
void setName(char * name);
void getName(char * name);
void averageSalary(CEmployee e1, CEmployee e2);
};
void CEmployee::setName(char * name){
strcpy(szName, name); //①
}
void CEmployee::getName(char * name){
strcpy(name, szName); //①
}
void CEmployee::averageSalary(CEmployee e1, CEmployee e2){
cout << e1.szName; //②
salary = (e1.salary + e2.salary) / 2;
}
void CEmployee::f(){
...
}
int main(){
CEmployee e;
strcpy(e.szName, "Tom1234567890"); //③
e.f(); //③
e.setName("Tom");
e.salary = 5000;
return 0;
}
class ClassName{
void f(int a = 0){
...
}
void f(){
...
}
};
int main(){
ClassName o;
o.f(); //error
}
- 成员函数的一种
- 名字与类名相同
- 可以有参数,但不能有返回值(void 也不行)
- 先生成对象(存储空间),再调用构造函数初始化(先买房再装修)
构造函数在对象数组中的使用
pass;
复制构造函数
只有一个参数,即对同类对象的引用(参数必须是引用)
没有定义复制构造函数,编译器也会生成默认的复制构造函数(默认构造函数不一定存在,复制构造函数一定会存在??)
使用:
ClassName(ClassName & o){
...
}
常引用(一般不会去修改,故一般使用常引用):
ClassName(const ClassName & o){
this_a = o.a;
this_b = o.b;
...
}
class ClassName{
...
};
ClassName o1; //调用缺省无参构造函数
ClassName o2(01); //调用缺省的复制构造函数,将o2初始化和o1一样
复制构造函数起作用的三种情况:
ClassName o2(o1);
等价于:ClassName o2 = o1;
//初始化语句,而非赋值语句ClassName o1, o2;
o2 = o1; //赋值语句,不调用赋值构造函数
void func(ClassName o1){
...
}
int main(){
ClassName o2;
func(o2); //调用 "class ClassName" 的复制构造函数
...
}
ClassName func(){
ClassName o;
return o;
}
int main(){
cout << func().a <<endl;
...
}
常量引用参数的使用:
const
关键字,使传入的参数只读(类似于映像,既不是副本,又不是原变量换名)Q.为什么要自己写复制构造函数?
!类型转换构造函数:
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数
当需要时,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)
目的:实现类型的自动转换
class Comples{
public:
double real, imag;
Complex(int i){ //类型转换构造函数
real = i; imag = 0;
}
Complex(double r, double i){
real = r; imag = i;
};
int main(){
Complex o1(7, 8);
Complex o2 = 12; //调用转换构造函数
c1 = 9; //9被自动转换成一个临时的Complex对象
...
}
析构函数(destructors)
- 在对象消亡时起作用(自动调用),对象消亡前作善后工作,比如释放分配的空间…
- 消亡一个对象,调用一次析构函数
- 名字与类名相同,在前面加~
- 没有参数和返回值
- 一个类最多只能有一个析构函数
- 定义时没写,会自动生成缺省析构函数,(什么也不做),定义了析构函数就不再生成析构函数
- 拆房子前,把值钱的东西都搬走
class String{
private:
char * p;
public:
String(){
p = new char[10];
}
~ String();
};
String::String(){
delete [] p;
}
delete运算会导致调用析构函数:
ClassName * p;
p = new ClassName;
delete p; //调用析构函数
p = new ClassName[3]; // 调用构造函数3次
delete [] p; //调用析构函数3次
tips: new一个对象数组时,要用delete [] p,否则只delete一个对象
tips: return出的临时对象使用完(执行完对应语句)也要消亡→调用析构函数
!!构造函数和析构函数什么时候被调用?
- 先构造后析构
dev c++中优化:不生成返回值临时对象
class ClassName{
int i;
public:
void Hello(){
cout << "Hello" <<endl;
}
};
int main(){
ClassName * p = NULL;
p->Hello();
}
输出:Hello
Ⅱ.而将成员函数Hello()
改为:
Hello(){
cout << i << "Hello" <<endl;
}
就会出错!
原因:Ⅰ中Hello()函数自带一个ClassName * this参数(类似于python里类的每个成员函数第一个参数必须是self)传进去一个指针可以执行Hello()函数,而Ⅱ中传进p以后,输出的i是p->i,p指向NULL,不存在i,所以错误
- 应用在成员函数中
- 作用就是指向成员函数所作用的对象
class ClassName{
int a;
ClassName func(){
this->a++;
return * this;
};
int main(){
ClassName o1, o2;
o2 = o1.func();
...
}
在定义时,前面加一个
static
关键字的成员
区别Ⅰ:
成员 | 作用 | 备注 |
---|---|---|
普通成员变量 | 每个对象有各自的一份 | 每个对象自己的属性 |
静态成员变量 | 所有对象共享,一共就一份 | 所有对象共同的属性 |
区别Ⅱ:
sizeof运算符不会计算静态成员变量
类名::成员名
ClassName::MemberVariable
ClassName::MemberFunction();
对象名.成员名
ClassName obj;
o.memberVariable;
o.MemberFunction();
//只是一种形式,静态成员函数并不作用在对象上
ClassName obj;
ClassName * p = & obj;
p->MemberVariable;
p->MemberFunction();
ClassName obj;
ClassName & reference = obj;
reference.MemberVariable;
reference.MemberFunction();
- 有成员对象(类中有成员是其他类的对象,类间调用)的类叫 封闭(enclosing)类
任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的(即成员对象必须有相应的构造函数)
具体做法:通过封闭类的构造函数的初始化列表
初始化列表中的参数可以是任意复杂的表达式,可以包括函数,变量,只要表达式中的函数或变量有定义就行
!!封闭类构造函数和析构函数的执行顺序
const
关键字(只读)const
关键字class ClassName{
public:
void function() const;
...
};
void ClassName::function() const{
...
}
作用:执行期间,不应修改其作用的对象
\ 不能修改成员变量的只(静态成员变量除外)
\ 不能调用同类的非常量成员函数(静态成员函数除外)
\常量成员函数的重载:
a:
int f() const{
...
}
b:
int f(){
...
}
a和b为重载关系
调用时:
用常量对象const ClassName obj
调用 f(),将会调用 int f() const
//因为常量对象不能执行非常量成员函数
用对象ClassName obj
调用 f(),将会执行 int f()
- 友元分为友元函数和友元类
- 朋友,可以访问私有
- 可以声明有哪些非本类的成员函数,可以访问本类的私有变量(赋予访问权限)
一个类的友元函数可以访问这个类的私有成员
不是这个类的成员函数,可以是一个普通的全局函数,声明为友元后就可以访问这个类的私有成员了
#include
using namespace std;
class CCar;
//提前声明CCar类,以便后面的CDriver类使用
//因为CCar类和CDriver类都互相调用了对方,谁放前面都不好,故提前声明一个
class CDriver{
public:
void ModifyCar(CCar * pCar); //改装汽车
};
class CCar{
private:
int price;
friend int MostExpensiveCar(CCar cars[], int total); //声明友元
friend void CDriver::ModifyCar(CCar * pCar); //声明友元
};
void CDriver::ModifyCar(CCar * pCar){
pCar->price += 1000; //改装后汽车价值增加
}
int MostExpensiveCar(CCar cars[], int total){
//求最贵汽车的价格
int tmpMax = -1;
for(int i = 0; i < total; ++i)
if(cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
int main(){
return 0;
}
两种友元函数:
普通的全局函数;
将一个类的成员函数(包括构造、解析函数)说明为另一个类的友元
class A{
public:
void function();
};
class B{
friend void A::function();
};
class A{
private:
int a;
friend class B;
}
class B{
public:
A obj;
int f(){
obj.a++;
...
}
};
//B生成对象的函数f()就可以调用A的私有成员变量a
总结:
增加私有成员的可访问范围
注意:
友元类之间的关系不能传递下去,也不能继承给子类
缺:对象间的运算符
补:基本运算符重载,扩展新的规则
#include ;
#include ;
using namespace std;
class Complex{
public:
double real, imag;
Complex(double r=0.0, double i=0.0) : real(r), imag(i){
//构造函数:将实部与虚部都初始化为0
}
Complex operator -(const Complex & c); //重载为成员函数
};
Complex operator +(const Complex & a, const Complex & b){ //重载为普通函数(全局函数)
return Complex(a.real + b.real, a.imag + b.imag); //返回一个临时对象:用 实部=a.r+b.r,虚部=a.i+b.i初始化
}
Complex Complex::operator -(const Complex & c){ //成员函数,只需要一个参数(参数个数 = 运算符个数-1)
return Complex(real - c.real, imag - c.imag); //返回一个临时对象
// real - c.real :当前对象(this.real)- 参数对象(c.real)
}
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;
}
缺:= 两边类型不匹配(int a = double b)
补:将 = 重载(定义新规则)
obj.operator +(x)
,而不能是operator +(obj, x)
#include ;
#include ;
using namespace std;
class String{
private:
char * str; //指向动态分配的存储空间
public:
String() : str(new char[1]){
//构造函数 new一个只有一个元素的char数组,用该数组的指针(数组名)初始化str
str[0] = 0;
}
const char * c_str(){
return str;
};
String & operator =(const char *s); //赋值运算符重载
//Q:为什么返回的是 String &
~String(){
//析构函数
delete [] str;
}
};
String & String::operator =(const char * s){
//重载 = 使obj = “hello”能够成立
delete [] str;
str = new char[strlen(s)+1];
strcpy(str, s);
return * this;
}
int main(){
String s;
s = "Good Luck, "; //等价于s.operator = ("Good Luck, ");
cout << s.c_str() <<endl;
//String s2 = "Hello!"; //这是初始化语句,没写构造函数,所以会出错
return 0;
}
s.operator =("Good Luck, ");
的运行过程:代码 | 过程 |
---|---|
1.delete [] str; |
先将String对象s的str delete掉(重置) |
2. str = new char[strlen(s)+1]; |
为str重新分配strlen(s)+1(+1放:\0) 大小的空间(重定向) |
3. strcpy(str, s); |
将参数(字符串"Good Luck, ")的内容拷贝过来 |
4.return * this; |
以后讲!!pass |
[Error] extra qualification 'String::' on member 'String' [-fpermissive]
→ Reason: The qualification student::
is required only if you define the member functions outside the class, usually in .cpp file.
String s1, s2;
s1 = "this";
s2 = "that";
s1 = s2
就出问题了!最后一条s1 = s2
,如果没有重载=,也能执行,相当于将对象s2赋值给对象s1(完全复制):s1.str对自己指的"this"始乱终弃,去指s2.str指的"that"了
后果:1. 2. 3.
3.如果再次给s1赋值,调用operator=( ) 就会 delete掉str指向的空间,就把s2的空间删了
MyString s;
s = "Hello";
s = s;
→重载函数中增加一个判断:
if(this == &s) //this指向当前对象
return * this;
对运算符进行重载的时候,好的风格是应该保持运算符原本的特性
考虑到a = b = c;
:
先进行 b = c
,如果operator =
返回值类型是void,接下来就成了 a = void
就出问题了
a = b = c;
等价于 a.operator =(b.operator(c))
(a = b) = c;
a=b的返回值是a的引用,
cpp中赋值运算符的返回值是=左边变量的引用(要维持这个性质),
对a的引用赋值成c,变成a 的值与c 的值一样,与b没关系了
会修改a的值:???所以不能是String
(a = b) = c;
等价于(a.operator =(b)).operator(c)
所以返回值类型应该是引用:String &
String(String & s){
str = new char[strlen(s.str)+1]'
strcpy(str, s.str);
}
class Complex{
double real, imag;
public:
Complex(double r, double i):real(r), imag(i){};
Complex operator + (double r);
};
Complex Complex::operator +(double r){
return Complex(real + r, imag);
}
这时:
c + 5
就等价于c.operator +(5)
但是:
5 + c
就不行了,这时类里已经有一个重载函数了,不能再在类里重载了,所以将+重载为普通函数,来处理这种情况:
Complex operator +(double r, const Complex & c){
return Complex(c.real + r, c.imag);
}
但是:普通函数又不能调用类中的私有成员(real、imag),所以需要将+重载为友元(就是将普通重载函数,或者说全局函数: operator +(...)
在类里声明为友元):
class Complex{
public:
friend Complex operator +(...);
...
};
== 标准模板库中的vector
int main(){
//开始数组为空
CArray a;
//1. 2.
for(int i=0; i<5; ++i){
a.push_back(i);
}
CArray a2, a3;
for(int i=0; i<a.length(); ++i){
cout << a2[i] << " ";
}
//3.
a2 = a3;
for(int i=0; i<a2.length(); ++i){
//4.
cout << a2[i] << " ";
}
a[3] = 100;
//5.
CArray a4(a);
for(int i=0; i<a4.length(); ++i){
cout << a4[i] << " ";
}
return 0;
}
需求:
- 动态分配内存来存放数组元素
- 需要一个指针成员变量
- 重载"=":用于数组间赋值,如a2 = a;
- 重载"[]":可以直接通过a[i]输出数组内元素
- 复制构造函数:可以通过a4(a)直接使a4复制a构造
class CArray{
int size; //数组元素的个数
int *ptr; //2.指向动态分配的数组
public:
CArray(int s=0); //s代表数组元素的个数
CArray(CArray & a);
~CArray();
CArray & operator =(const CArray & a); //3.用于数组对象间的赋值
CArray & operator [](int i); //4.重载[]
void push_back(int v); //在数组的尾部添加一个元素v
int length(){
return size;
}
//构造函数
CArray::CArray(int s):size(s){
if(s == 0){
ptr = NULL;
}
else{
ptr = new int[s];
}
}
//5.复制构造函数,深拷贝
CArray::CArray(CArray & a){
if(!a.ptr){
ptr = NULL;
size = 0;
return;
}
ptr = new int[a.size];
memcpy(ptr, a.ptr, sizeof(int) * a.size);
size = a.size;
}
//析构函数
CArray::~CArray(){
if(ptr){
delete []ptr;
}
}
//3.
CArray & CArray::operator =(const CArray & a){
if(ptr == a.ptr){
//防止a = a这样的赋值导致出错
return * this;
}
if(a.ptr == NULL){
if(ptr){
delete []ptr;
}
ptr = NULL;
size = 0;
return * this;
}
if(size < a.size){
//如果原有空间足够大,就不需要重新分配空间
if(ptr){
delete []ptr;
}
ptr = new int[a.size];
}
memcpy(ptr, a.ptr, sizeof(int) * a.size);
size = a.size;
return * this;
}
//4.
int & CArray::operator [](int i){
//用以支持根据下标访问数组元素,如a[i] = 4...
return ptr[i];
}
void CArray::push_back(int v){
//在数组尾部添加一个元素
if(ptr){
int * tmpPtr = new int[size+1]; //重新分配空间
memcpy(tmpPtr, ptr, sizeof(int) * size); //拷贝原数组内容
delete []ptr;
ptr = tmpPtr;
}
else{
//数组本来是空的
ptr = new int[1];
}
ptr[size++] = v; //加入新的元素
}
};
在C++中:">>“与”<<“本质上就是左移、右移运算符,但因经常用于cout,cin,所以被称为,流插入运算符(”>>")和流提取运算符("<<")
Q:cout << 5 << "this";
为什么能成立?
A:
cou
t是在头文件iostream
中定义的一个ostream
类对象<<
能用在cout上的原因是,<<
在iostream
中进行了重载cout << 5;
等价于 cout.operator <<(5);
cout << "this";
等价于cout.operator <<("this");
cout << 5 << "this";
第一次运算cout << 5
的返回值类型仍为cout才能继续进行cout << "this";
ostream & ostream::operator <<(int n){
...//输出n的代码
return * this;
}
ostream & ostream::operator <<(const char * s){
...//输出s的代码
return * this;
}
cout << 5 << "this";
等价于cout.operator <<(5).operator <<("this");
如果要重载cout,定义新的规则,必须重载为全局函数,如:
class CStudent{
public:
int nAge;
};
int main(){
CStudent s;
s.nAge = 5;
cout << s << "Hello";
return 0;
}
想使cout << s
直接输出s.nAge
,并返回一个cout(即ostream对象),则应:
ostream & operator <<(ostream & o, const CStudent & s){
o << s.nAge;
return o;
}
类型名字就是类型转换运算符,如double,可以使用(double)来强制类型转换
#include
using namespace std;
class Complex{
double real, imag;
public:
Complex(double r=0, double i=0): real(r), imag(i){};
//重载强制类型转换运算符
operator double(){
return real;
}
};
int main(){
Complex c(1.2, 3.4);
cout << (double)c <<endl;//输出 1.2
//等价于c.operator double()
double n = 2 + c;
//等价于double n = 2 + c.operator double()
cout << n;//输出 3.2
Q:前置++/–,与后置的区分:
A:
T & operator ++();
T & operator --();
T1 & operator ++(T2);
T1 & operator --(T2);
T & operator ++(int);
T & operator --(int);
T1 & operator ++(T2, int);
T1 & operator --(T2, int);
实例:
class CDemo{
private:
int n;
public:
CDemo(int i=0):n(i){}
CDemo & operator ++();//前置(++a返回的就是a的引用,因为++a是先对a做++,返回a的引用(小名),就可以直接对++完的a进行操作了)
CDemo operator ++(int);//后置
operator int(){
return n;
}
friend CDemo & operator --(CDemo &);
friend CDemo operator --(CDemo &, int);
};
//······································重载为成员函数··············································
CDemo & CDemo::operator ++(){
//前置++
++n;
return * this;
}
//++s即为:s.operator ++();
CDemo CDemo::operator(int k){
//后置++
CDemo tmp(*this);//记录修改前的对象
n++;
return tmp;//返回修改前的对象(后置++返回的不是a,a++前代替a的临时对象tmp,所以返回值类型为对象(tmp))
}
//s++即为:s.operator ++(0);
//······································重载为全局函数··············································
CDemo & operator --(CDemo & d){
//前置
d.n--;
return d;
}
//--s即:s.operator ++(0);
CDemo operator --(CDemo & d, int){
//后置--
CDemo tmp(d);
d.n--;
return tmp;
}
//s--即:operator --(s, 0);
tips:前置++/–运行速度快!(并未构造新的临时对象tmp)
.
、.*
、::
、?:
、sizeof
()
、[]
、->
或者=
时,运算符重载函数必须声明为类的成员函数
- 派生类(子类)是通过对基类进行修改(覆盖)和扩充得到的
- 在派生类中,拥有基类的全部成员函数和成员变量(private、protected、public:但是!子类的成员函数中不能访问这些private成员)可以扩充新的成员变量和函数
- 在派生类对象中,包含着基类对象,而且基类对象存储位置位于派生类对象新增的成员变量之前
- 派生类一经定义后,可以单独使用,不依赖于基类
写法:
class 派生类名: public 基类名{
...
};
调用基类成员:
基类名::成员函数名();
实例:学籍管理
#include
#include
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;}
};
void CStudent::PrintInfo(){
cout<< "Name:" << name <<endl;
cout<< "ID:" << id <<endl;
cout<< "Gender:" << gender <<endl;
cout<< "Age:" << age <<endl;
}
void CStudent::SetInfo(const string & name_, const string & id_,
int age_, char gender_){
name = name_;
id = id_;
age = age_;
gender = gender_;
}
class CUndergraduateStudent: public CStudent{
private:
string department;
public:
void QualifiedForBaoyan(){
cout << "qualified 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_);
department = department_;
}
};
int main(){
CUndergraduateStudent s2;
s2.SetInfo("Fjj", "112233", 19, 'M', "CS");
cout << s2.GetName() << " ";
s2.QualifiedForBaoyan();
s2.PrintInfo();
return 0;
}
class CDog; //提前声明
class CMaster{
CDog dogs[10];
};
class CDog{
CMaster m;
};
Wrong: 循环定义(编译出错),CDog对象和CMaster对象所占内存求不出(矛盾)
class CDog;
class CMaster{
CDog * dogs[10];
};
class CDog{
CMaster m;
};
Wrong:无法维护不同狗的相同主人信息(一群主人如果更换了,只能挨个狗改)
class CMaster; //必须先写CDog类,后写CMaster类
class CDog{
CMaster * pm;
};
class CMaster{
CDog dogs[10];
};
Wrong:还凑合,但狗失去了自由,成为主人的固有属性了,只能通过人对象才能操作狗对象
class CMaster;
class CDog{
CMaster * pm;
};
class CMaster{
CDog * dogs[10];
};
Right:互为对象指针:“知道”关系(一个类的对象可以通过指针找到另一个对象)
覆盖:派生类可以定义一个和基类成员同名的成员
class Father{
private:
int nPrivate;
public:
int nPublic;
protected:
int nProtected;
};
class Son: public Father{
void AccessFather(){
nPublic = 1; //T
nPrivate = 1; //F
nProtected = 1; //Error
Son f;
f.nProtected = 1; //Error:①f不是当前对象
};
int main(){
Father f;
Son s;
f.nPublic = 1; //T
s.nPublic = 1; //T
f.nProtected = 1; //Error
f.nPrivate = 1; //Error
s.nProtected = 1; //Error
s.nPrivate = 1; //Error
return 0;
}
class Bug{
private:
int nLegs;
int nColor;
public:
int nType;
Bug(int Legs, int color);
void PrintBug(){};
};
class FlyBug: public Bug{
int nWings;
public:
FlyBug(int legs, int color, int wings);
};
//Bug的构造函数
Bug::Bug(int legs, int color){
nLegs = legs;
nColor = color;
}
//FlyBug的错误构造函数
FlyBug::FlyBug(int legs, int color, int wings){
nLegs = legs; //Error:nLegs是private的
nColor = color; //Error
nType = 1;
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug(int legs, int color, int wings): Bug(legs, color){
nWings = wings; //也可以直接加在初始化列表中: Bug(legs, color), nWings(wings)
}
:Bug(legs, color)
:初始化列表(在派生类里直接初始化所包含的基类对象)class Bug{
private:
int nLegs;
int nColor;
public:
int nType;
Bug(int Legs, int color);
void PrintBug(){};
};
class Skill{
public:
Skill(int n){}
};
class FlyBug: public Bug{
int nWings;
Skill sk1, sk2;
public:
FlyBug(int legs, int color, int wings);
};
FlyBug::FlyBug(int legs, int color, int wings): Bug(legs, color), sk1(5), sk2(color), nWings(wings){
}
class Base{
...
};
class Derived: public Base{
...
};
Base b;
Derived d;
规则1. 派生类对象可以赋值给基类对象(“是”关系):b = d; //缺省情况下:将d中包含的基类对象拷贝给b
规则2. 派生类对象可以初始化基类引用:Base & br = d; //br引用了d中包含的基类对象(别名)
规则3. 派生类对象的地址可以赋值给基类对象的指针:Base * pb = & d; //基类指针指向派生类对象(== 指向派生类对象存储空间最前面)
TIPS:如果派生方式是:private、protected,规则1.2.3.不成立
#include
using namespace std;
class Base{
public:
int n;
Base(int i): n(i){
cout << "Base " << n << " constructed" <<endl;
}
~Base(){
cout << "Base " << n << " destructed" <<endl;
}
};
class Derived:public Base{
public:
Derived(int i): Base(i){
cout << "Derived constructed" <<endl;
}
~Derived(){
cout << "Derived destruct" <<endl;
}
};
class MoreDerived: public Derived{
public:
MoreDerived():Derived(4){
//①
cout << "More Derived constructed" <<endl;
}
~MoreDerived(){
cout << "More Derived destructed" <<endl;
}
};
int main(){
MoreDerived Obj;
return 0;
}
输出结果:
Base 4 constructed
Derived constructed
More Derived constructed
More Derived destructed
Derived destruct
Base 4 destructed
virtual
关键字的成员函数(虚函数可以参与多态,普通函数不能)class base{
virtual int get();
};
int base::get(){} //①
派生类指针可以赋值给基类指针
通过基类指针调用基类和派生类中的同名虚函数时:
- 1)若该指针指向一个基类的对象,那么被调用的是基类的虚函数
- 2) 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数
实例:
class CBase{
public:
virtual void SomeVirtualFunc(){}
};
class CDerived: public CBase{
public:
virtual void SomeVirtualFunc(){}
};
int main(){
CDerived ODerived;
CBase * p = & ODerived;
//这里p指向的是ODerived对象
p -> SomeVirtualFunc();
//调用哪个虚函数,取决于p指向对象的类型,所以这里调用CDerived中的虚函数
return 0;
}
class CBase{
public:
virtual void SomeVirtualFunc(){}
};
class CDerived: public CBase{
public:
virtual void SomeVirtualFunc(){}
};
int main(){
CDerived ODerived;
CBase & r = ODerived;
//这里r引用的是ODerived对象
p -> SomeVirtualFunc();
//调用哪个虚函数,取决于r引用对象的类型,所以这里调用CDerived中的虚函数
return 0;
}
#include
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;
}
};
class D: public A{
public:
virtual void Print(){
cout << "D::Print" <<endl;
}
};
class E: public B{
virtual void Print(){
cout << "E::Print" <<endl;
}
};
int main(){
A a;
B b;
E e;
D d;
A *pa = &a;
B *pb = &b;
D *pd = &d;
E *pe = &e;
pa -> Print();
pa = pb;
pa -> Print();
pa = pd;
pa -> Print();
pa = pe;
pa -> Print();
return 0;
}
继承关系图:
结果:
A::Print
B::Print
D::Print
E::Print
纯虚函数:没有函数体,virtual void func() = 0;
,父类只提供个平台,父类本身用不到这些虚函数(见5.4)
TIPS:用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法
实例:
问题描述:输入若干几何形体参数,按面积排序输出
Sample Input: 3 //图形数 R 3 5 //矩形 C 9 //圆 T 3 4 5 //三角形
Sample Output: Triangle:6 Rectangle:15 Circle:254.34
#include
#include
#include
using namespace std;
class CShape{
public:
virtual double Area() = 0;
virtual void PrintInfo() = 0;
};
class CRectangle: public CShape{
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle: public CShape{
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle: public CShape{
public:
int a, b, c;
virtual double Area();
virtual void PrintInfo();
};
double CRectangle::Area(){
return w * h;
}
void CRectangle::PrintInfo(){
cout << "Rectangle:" << Area() <<endl;
}
double CCircle::Area(){
return 3.14 * r * r;
}
void CCircle::PrintInfo(){
cout << "Circle:" << Area() <<endl;
}
double CTriangle::Area(){
double p = (a + b + c) / 2.0;
return sqrt(p * (p - a) * (p - b) * (p - c));
}
void CTriangle::PrintInfo(){
cout << "Triangle:" << Area() <<endl;
}
CShape * pShapes[100]; //①基类指针数组
int MyCompare(const void * s1, const void * s2);
int main(){
int i;
int n;
CRectangle * pr;
CCircle * pc;
CTriangle * pt;
cin >> n;
for(i = 0; i < n; i++){
char c;
cin >> c;
switch(c){
case'R':
pr = new CRectangle();
cin >> pr -> w >> pr -> h;
pShapes[i] = pr;
break;
case'C':
pc = new CCircle();
cin >> pc -> r;
pShapes[i] = pc;
break;
case'T':
pt = new CTriangle();
cin >> pt -> a >> pt -> b >> pt -> c;
pShapes[i] = pt;
break;
}
}
qsort(pShapes, n, sizeof(CShape*), MyCompare);
for(i=0; i<n; i++){
pShapes[i] -> PrintInfo();
}
return 0;
}
int MyCompare(const void * s1, const void * s2){
double a1, a2;
CShape * * p1; //s1, s2是void *, 不可写 " *s1 " 来取得s1的内容
CShape * * p2;
p1 = (CShape * *) s1; //s1,s2指向pShapes数组中的元素,素组元素的类型是CShape *
p2 = (CShape * *) s2; //故p1, p2都是指向指针的指针,类型为CShape **
a1 = (*p1) -> Area(); //* p1的类型是Cshape *,是基类指针,故此句为多态
a2 = (*p2) -> Area();
if(a1 < a2)
return -1;
else if(a2 < a1)
return 1;
else
return 0;
}
#include
using namespace std;
class Base{
public:
void fun1(){
fun2();
//② 相当于this -> fun2(): this为Derived对象d ①
}
virtual void fun2(){
cout << "Base:fun2()" <<endl;
}
};
class Derived: public Base{
public:
virtual void fun2(){
cout << "Derived:fun2()" <<endl;
}
};
int main(){
Derived d;
Base *pBase = &d;
//① pBase指针指向的是一个Derived对象d
pBase -> fun1();
return 0;
}
结果:Derived:fun2()
#include
using namespace std;
class Base{
public:
virtual void hello(){
cout << "hello from Base-1" <<endl;
}
virtual void bye(){
cout << "bye from Base-1" <<endl;
}
};
class Derived: public Base{
public:
void hello(){
//②
cout << "hello from Derived-2" <<endl;
}
Derived(){
hello(); //①构造函数中调用虚函数不是多态,所以这里直接调用Derived自己的hello
}
~Derived(){
bye();
}
};
class MoreDerived: public Derived{
public:
void hello(){
cout << "hello from MoreDerived-3" <<endl;
}
void bye(){
cout << "bye from MoreDerived-3" <<endl;
}
MoreDerived(){
cout << "constructing MoreDerived-3" <<endl;
}
~MoreDerived(){
cout << "destructing MoreDerived-3" <<endl;
}
};
int main(){
MoreDerived md3; //③
Derived *pd2 = & md3;
pd -> hello(); //④ 是多态
return 0;
}
结果:
//③:
hello from Derived-2
constructing MoreDerived-3
//④:
hello from MoreDerived-3
//消亡:
destructing MoreDerived-3
bye from Base-1
Why?
比如说,new一个对象时,会先调用基类的构造函数(这时这个对象所属的派生类还没有初始化),如果构造函数中的虚函数是多态,调用基类的构造函数中的虚函数时,实际调用的是该派生类中对应的虚函数,而派生类这时还没有初始化好,没法被调用
virtual
,这样通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数:见:实例②#include
using namespace std;
class Base{
public:
~Base(){
cout << "bye from Base-1" <<endl;
}
};
class Derived: public Base{
public:
~Derived(){
cout << "bye from Derived-2" <<endl;
}
};
int main(){
Base *pb;
pb = new Derived();
delete pb;
return 0;
}
输出:
bye from Base-1
//只执行了基类(Base)的虚构函数,没有执行派生类(Derived)的虚构函数
virtual ~son(){...};
输出结果:
bye from Derived-2
bye from Base-1
//析构自底向上
class A{
private:
int a;
public:
virtual void Print() = 0;
void fun(){ cout << "fun";}
};
#include
using namespace std;
class A{
public:
virtual void f() = 0;
void g(){
this -> f(); //①
}
A(){
//f(); //②
}
};
class B: public A{
public:
void f(){ // ③
cout << "B:f()" <<endl;
}
};
int main(){
B b;
b.g();
return 0;
}
输出结果:B:f()
①:多态,取决于作用的对象(如果作用对象为A,但A的f()是纯虚函数,就出错,但这种情况不会发生,因为A为抽象类吧,不能用抽象类定义对象),这里this 指向的是b,调用B的f()
②:编译出错,构造函数中调用虚函数,不是多态,只能调用自己的虚函数,而A的f()为纯虚函数,连函数体都没有,没法调用
③:B实现了A全部的纯虚函数,只有f(),所以B不是抽象类,可以创建对象b,如果没有实现f(),则B b;
会出错: [Error] cannot declare variable 'b' to be of abstract type 'B'
类 | 作用 | 对象 |
---|---|---|
istream |
输入的流类 | cin |
osream |
输出的流类 | cout |
ifstream |
从文件读取数据的类 | |
ofstream |
向文件写入数据的类 | |
iostream |
既用于输入,又用于输出 | |
fstream |
既能从文件读取数据,又能向文件写入数据 |
标准流对象:
- 输入流对象:cin
(用于从键盘读取数据,也可以重定向为从文件中读取数据)
- 输出流对象:cout
(用于向屏幕输出数据,也可以重定向为向文件写入数据)、cerr
、 clog
(对应于标准错误输出流,用于向屏幕输出错误信息,cerr
不用缓冲区,而clog
先存缓冲区,刷新或区满才输出到屏幕)
输出重定向
#include
using namespace std;
int main(){
int x, y;
cin >> x >> y;
freopen("test.txt", "w", stdout); //将标准输出(stdcout)重定向到test.txt文件
if(y == 0)
cerr << "error." <<endl; //cerr没有被重定向
else
cout << x / y; //输出结果到test.txt
return 0;
}
#include
using namespace std;
int main(){
double f;
int n;
freopen("t.txt", "r", stdin); //cin被改为从t.txt中读取数据
cin >> f >> n;
cout << f << "," << n <<endl;
return 0;
}
int x;
while(cin >> x){
...
}
return 0;
freopen("some.txt", "r", stdin);
,读到文件尾部,输入流就算结束Ctrl
+ z
代表输入流结束istream
类的成员函数:
1) istrem & getline(char * buf, int buffSize);
从输入流中读取bufSize-1
个字符到缓冲区buf,或读到碰到'\n'
为止(两个条件哪个先满足,按哪个来)
2)istream & getline(char * buf, int bufSize, char delim);
从输入流中读取bufSize-1
个字符到缓冲区,或碰到delim
字符(分隔符)为止(满足其一即可)
- 两个函数都会自动在buf中读入数据的结尾添加
\0
\n
或delim
都不会被读入buf,但会被从输入流中取走。如果输入流中\n
或delim
之前的字符个数达到或超过了bufSize
个,就导致读入出错,其结果就是:虽然本次读入已经完成,但是之后的读入就会失败了- 可以用
if(!cin.getline(...))
判断输入是否结束
3)bool eof();
判断输入流是否结束
4)int peek();
返回下一个字符,但不从流中去掉
5)istream & putback(char c);
将字符ch放回输入流(头部,最前面)
6)istream & ignore(int nCount = 1, int delim = EOF);
从流中删掉最多nCount个字符,遇到EOF时结束
流操作纵算子:
#include
dec
(十进制)、oct
(八进制)、hex
(十六进制)、setbase
(任意进制)见:实例1#include
#include
using namespace std;
int main(){
int n = 10;
cout << n <<endl;
cout << hex << n << "\n"
<< dec << n << "\n"
<< oct << n << endl;
return 0;
}
10 //原
a //十六进制
10 //十进制
12 //八进制
precision
、setprecision
见:实例2cout.precision(5);
setprecision是流操作算子,调用方式为:
cout << setprecision(5); //可以连续输出(设置后,后面的浮点数都按这个精度来)
功能相同:指定输出浮点数的有效位数(非定点方式输出时)
指定输出浮点数的小数点后的有效位数(定点方式输出时)
定点方式:小数点必须出现在个位后面
TIPS:浮点数输出最多6位有效数字
#include
#include
using namespace std;
int main(){
double x = 1234567.89, y = 12.34567;
int n = 12234567;
int m = 12;
cout << setprecision(6) << x << endl
<< y << endl << n << endl << m;
return 0;
}
1.23457e+006
12.3457
12234567
12
#include
#include
using namespace std;
int main(){
double x = 1234567.89, y = 12.34567;
int n = 12234567;
int m = 12;
//setiosflags以小数点位置固定的方式输出
cout << setiosflags(ios::fixed) <<
setprecision(6) << x << endl
<< y << endl << n << endl << m;
return 0;
}
1234567.890000
12.345670
12234567
12
TIPS:用resetiosflags(ios::fixed)
来取消定点模式
cout << setiosflags(ios::fixed) <<
setprecision(6) << x << endl <<
resetiosflags(ios::fixed) << x;
setw
、width
(按一定宽度输出,不足补空格/0) 见:实例3setw
、width
功能一样,一个是成员函数,一个是流操作算子,调用方式不同:cin >> setw(4);
cin.width(5);
cout << setw(4);
cout.width(5);
TIPS:设置宽度是一次性的,每次读入和输出之前都要设置宽度
#include
#include
using namespace std;
int main(){
int w = 4;
char string[10];
cin.width(5);
while(cin >> string){
cout.width(w++);
cout << string <<endl;
cin.width(5);
}
return 0;
}
1234567890
1234
5678
90
#include
#include
using namespace std;
int main(){
int n = 141;
//1)分别以十六进制,十进制,八进制先后输出n
cout << "1) " << hex << n << " " << dec << n << " " << oct << n <<endl;
double x = 1234567.89, y = 12.34567;
//2)保留5位有效数字
cout << "2) " << setprecision(5) << x << " " << y << " " <<endl;
//3)保留小数点后面5位
cout << "3) " << fixed << setprecision(5) << x << " " << y <<endl;
//4)科学计数法输出,且保留小数点后面5位
cout << "4) " << scientific << setprecision(5) << x << " " << y << endl;
//5)非负数要显示正号,输出宽度为12字符,宽度不足则用'*'填补
cout << "5) " << showpos << fixed << setw(12) << setfill('*') << 12.1 <<endl;
//6)非负数要显示正号,输出宽度为12字符,宽度不足则右边用填充字符填充
cout << "6) " << noshowpos << setw(12) << left << 12.1 <<endl;
//7)输出宽度为12字符,宽度不足则左边用填充字符填充
cout << "7) " << setw(12) << right << 12.1 <<endl;
//8)宽度不足时,负号和数值分列左右,中间用填充字符填充
cout << "8) " << setw(12) << internal << -12.1 <<endl;
cout << "9) " << 12.1 <<endl;
return 0;
}
1) 8d 141 215
2) 1.2346e+006 12.346
3) 1234567.89000 12.34567
4) 1.23457e+006 1.23457e+001
5) ***+12.10000
6) 12.10000****
7) ****12.10000
8) -***12.10000
9) 12.10000
ostream &tab(ostream &output){
return output << '\t';
}
cout << "aa" << tab << "bb" <<endl;
aa bb //中间空了个tab
!:iostream
里对<<
进行了重载(成员函数)
ostream & operator(ostream & (*p)(ostream &));
该函数内部会调用p所指向的函数,且以*this
作为参数,如hex、dec、oct…都是函数
#include
ofstream outFile("clients.dat", ios::out|ios::binary);
//创建文件
→ 也可以先创建ofstream对象,再用open函数打开(文件名可以给出绝对路径,也可以给出相对路径。没有交代路径信息,就是在当前文件夹下找文件)
ofstream fout;
fout.open("test.out", ios::out|ios::binary);
→判断打开是否成功:
if(!fout){
cout << "File open error!" <<endl;
}
ofstream fout("a1.out", ios::app); //以添加方式打开
long location = fout.tellp(); //取得写指针的位置
location = 10;
fout.seekp(location); //将写指针移动到第10个字节处(距文件开头)
fout.seekp(location, ios::beg); //从头数location
fout.seekp(location, ios::cur); //从当前位置数loaction
fout.seekp(location, ios::end); //从尾部数location
- location
可以为负值
ifstream fin("a1.in", ios::ate); //打开文件,定位文件指针到文件尾(位置就是文件大小)
long location = fin.tellg(); //取得读指针的位置(位置(相对)就是距文件头多少字节)
location = 10L;
fout.seekg(location); //将读指针移动到第10个字节处(距文件开头)
fout.seekg(location, ios::beg); //从头数location
fout.seekg(location, ios::cur); //从当前位置数loaction
fout.seekg(location, ios::end); //从尾部数location
#include
#include
#include
#include
using namespace std;
int main(){
vector<int> v;
ifstream srcFile("in.txt", ios::in);
ofstream destFile("out.txt", ios::out);
int x;
while(srcFile >> x)
//只有文件没读完,就不断将文件中内容读到x中
v.push_back(x);
sort(v.begin(), v.end());
for(int i=0; i<v.size(); i++)
destFile << v[i] << " ";
destFile.close();
srcFile.close();
return 0;
}
ifstream
和fstream
的成员函数:istream & read(char * s, long n);
:将文件读指针指向的地方的n个字节内容,读入到内存地址s,然后将文件读指针向后移动n字节(以ios::in方式打开文件时,文件读指针开始指向文件开头)
ofstream
和fstream
的成员函数:istream & write(const char * s, long n);
:将内存地址s处的n个字节内容,写人到文件中写指针指向的位置,然后将文件写指针向后移动n字节(以ios::out方式打开文件时,文件写指针开始指向文件开头,以ios::app方式打开文件时,文件写指针开始指向文件尾部)
#include
#include
using namespace std;
int main(){
ofstream fout("some.dat", ios::out|ios::binary);
int x = 20;
fout.write((const char *)(&x), sizeof(int));
fout.close();
ifstream fin("some.dat", ios::in|ios::binary);
int y;
fin.read((char *) & y, sizeof(int));
fin.close();
cout << y <<endl;
return 0;
}
#include
#include
using namespace std;
struct Student{
char name[20];
int score;
};
int main(){
Student s;
ofstream OutFile("C:\\Users\\Administrator\\Desktop\\students.dat", ios::out|ios::binary);
while(cin >> s.name >> s.score)
OutFile.write((char *) & s, sizeof(s));
OutFile.close();
return 0;
}
→读出该student文件:
#include
#include
using namespace std;
struct Student{
char name[20];
int score;
};
int main(){
Student s;
ifstream inFile("students.dat", ios::in|ios::binary);
if(!inFile){
cout << "error" <<endl;
return 0;
}
while(inFile.read((char *) & s, sizeof(s))){
int readedBytes = inFile.gcount(); //看刚才读了多少个字节
cout << s.name << " " << s.score <<endl;
}
inFile.close();
return 0;
}
→二进制文件的读写:更改二进制文件中的内容
#include
#include
#include //**是不包括strlen的,要使用cstring**
using namespace std;
struct Student{
char name[20];
int score;
};
int main(){
Student s;
fstream iofile("C:\\Users\\Administrator\\Desktop\\students.dat",
ios::in|ios::out|ios::binary);
if(!iofile){
cout << "error" <<endl;
return 0;
}
iofile.seekp(2 * sizeof(s), ios::beg); //s*sizeof(s):将写指针定位到第三个记录
iofile.write("xxx", strlen("xxx")+1);
iofile.seekg(0, ios::beg); //定位读指针到开头
while(iofile.read((char *) & s, sizeof(s))){
cout << s.name << " " << s.score <<endl;
}
iofile.close();
return 0;
}
二进制存储的优势:
1.节省空间(文本存储中会有回车/空格…的浪费;另外,如果存1000000,文本方式中为字符串,7位,占7个字节,而用二进制方式,只cp4字节的int型)
2.二进制格式,每个数据所占空间固定(等长、对齐),便于查找(二分查找…)
实例:文件拷贝程序 mycopy.cpp
/*用法示例:
mycopy src.dat dest.dat
即将 src.dat 拷贝到dest.dat 如果 dest.dat 原来就有,则原来的文件会被覆盖
*/
#include
#include
using namespace std;
int main(int argc, char * argv[]){
if(argc != 3){
cout << "File name missing!" <<endl;
return 0;
}
ifstream inFile(argv[1], ios::binary|ios::in); //打开文件用于读
if(!inFile){
cout << "Source file open error." <<endl;
return 0;
}
ofstream outFile(argv[2], ios::binary|ios::out); //打开文件用于写
if(!outFile){
cout << "New file open error." <<endl;
inFile.close(); //打开的文件一定要关闭
return 0;
}
char c;
while(inFile.get(c)) //每次读取一个字符,整个文件都读完退出while
outFile.put(c); //每次写入一个字符
outFile.close();
inFile.close();
return 0;
}
\n
(ASCII码:0x0a)\r\n
(ASCII码:0x0d0a) endl
就是\n
\r
(ASCII码:0x0d)Unix/Linux下打开文件,用不用ios::binary没区别
而在Windows下,如果不用ios::binary则:
- 读取文件时,所有\r\n
会被当成一个字符\n
处理,即少读一个字符\r
- 写入文件时,写入单独的\r
时,系统自动在前面加一个\r
,即多写一个\r
函数模板就是让编译器可以适应各种参数类型
template<class 类型参数1, class 类型参数2, ...>
返回值类型 模板名(形参表){
...
};
TIPS: class可以替换为typename
#include
using namespace std;
template <class T>
void Swap(T & x, T & y){
T tmp = x;
x = y;
y = tmp;
}
int main(){
int n = 1, m = 2;
Swap(n, m); //编译器自动生成void Swap(int &, int &)函数
cout << n << " " << m <<endl;
double f = 1.2, g = 2.3;
Swap(f, g); //编译器自动生成void Swap(double &, double &)函数
cout << f << " " << g <<endl;
return 0;
}
template<class T1, class T2>
T2 print(T1 arg1, T2 arg2){
cout << arg1 << " " << arg2 <<endl;
return arg2;
}
#include
using namespace std;
template <class T>
T MaxElement(T a[], int size){
T tmpMax = a[0];
for(int i=1; i<size; ++i)
if(tmpMax < a[i])
tmpMax = a[i];
return tmpMax;
}
int main(){
int s1[3] = {2, 9, 8};
cout << MaxElement(s1, 3) <<endl;
double s2[4] = {1.2, 2.5, 3.5, 0.9};
cout << MaxElement(s2, 4)<<endl;
return 0;
}
手动(强制)实例化函数模板,见:实例1#include
using namespace std;
template <class T>
T Inc(T n){
return 1 + n;
}
int main(){
cout << Inc<double>(4) / 2; //输出2.5
return 0;
}
①
template<class T1, class T2>
void print(T1 arg1, T2 arg2){
cout << arg1 << " " << arg2 <<endl;
}
②
template<class T>
void print(T arg1, T arg2){
cout << arg1 << " " << arg2 <<endl;
}
③
template<class T, class T2>
void print(T arg1, T arg2){
cout << arg1 << " " << arg2 <<endl;
}
在有多个函数和函数模板名字相同的情况下,对一条函数调用语句,编译器:
1)先找参数完全匹配的普通函数(非由模板实例化得来的函数)
2)再找参数完全匹配的模板函数
3)再找实参经过自动类型转换后能匹配的普通函数
4)以上都没有,报错!
实例:
#include
using namespace std;
double F(double a, double b){
cout << "1)参数匹配的普通函数" <<endl;
return 0;
}
template <class T>
T F(T a, T b){
cout << "2)参数完全匹配的模板函数" <<endl;
return 0;
}
template <class T, class T2>
T F(T a, T2 b){
cout << "3)实参匹配实例化后匹配的模板函数" <<endl;
return 0;
}
int main(){
int i = 4, j = 5;
F(1.2, 3.4); //输出1)
F(i, j); //输出2)
F(1.2, 3); //输出3)
// F(1, 2) //报错!
return 0;
}
template<class T>
T myFun(T arg1, T arg2){
cout << arg1 << " " << arg2 << "\n";
return arg1;
}
int main(){
myFun(5, 7); //ok
myFun(5.8, 8.4); //ok
myFun(5, 8.4); //[error]:no matching function for call to 'myFun(int, double)'
将区间[s, e)内的元素,通过变化op()后,放到以x为起始位置的一段空间
#include
using namespace std;
template<class T, class Pred>
void Map(T s, T e, T x, Pred op){
//一般s,e,x都是指针,而op为函数指针,指向变换函数
for(; s!=e; ++s, ++x)
//到e为止,e处元素未参与变化
*x = op(*s);
}
double Square(double x){return x * x;}
int Cube(int x){return x * x * x;}
int a[5] = {1, 2, 3, 4, 5}, b[5];
double c[5] = {1.1, 2.1, 3.1, 4.1, 5.1}, d[5];
int main(){
Map(a, a+5, b, Square);
for(int i=0; i<5; ++i) cout << b[i] << ",";
cout <<endl;
Map(a, a+5, b, Cube);
for(int i=0; i<5; ++i) cout << b[i] << ",";
cout <<endl;
Map(c, c+5, d, Square);
for(int i=0; i<5; ++i) cout << d[i] << ",";
cout <<endl;
return 0;
}
其中,Map(a, a+5, b, Square);
被实例化为:
void Map(int * s, int * e, int * x, double(*op)(double)){
for(; s!=e; ++s, ++x)
*x = op(*s);
}
把一些相似的类定义成类模板,再通过实际情况进行实例化
template<class 类型参数1, class 类型参数2, ...> //类型参数表
class 类模板名{
... //成员变量和函数
};
template<class 类型参数1, class 类型参数2, ...>
返回值类型 类模板名<类型参数名列表>::成员函数名(参数表){
...
}
类模板名<真实类型参数表> 对象名(构造函数实参表);
#include
using namespace std;
template<class T1, class T2>
class Pair{
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k, T2 v):key(k), value(v){};
bool operator < (const Pair<T1, T2> & p) const;
};
template<class T1, class T2>
bool Pair<T1, T2>::operator < (const Pair<T1, T2> & p) const{
//Pair 的成员函数operator <
return key < p.key;
}
int main(){
Pair<string, int> student("Tom", 19);
//实例化出一个类Pair
cout << student.key << " " << student.value;
return 0;
}
Pair<string, int> * p;
Pair<string, double> a;
p = & a; //wrong
#include
using namespace std;
template<class T>
class A{
public:
template<class T2>
void Fun(T2 t){cout << t;} //成员函数模板!
};
int main(){
A<int> a;
a.Fun('K'); //Func被实例化, T2 -> char
a.Fun("hello"); //Func再次被实例化,T2 -> const char *
return 0;
}
template<class T, int size>
class CArray{
T array[size];
public:
void Print(){
for(int i=0; i<size; ++i)
cout << array[i] <<endl;
};
CArray<double, 40> a2;
CArray<int, 50> a3; //a2 a3属于不同的类
#include
using namespace std;
template<class T1, class T2>
class A{
T1 v1;
T2 v2;
};
template<class T1, class T2>
class B: public A<T2, T1>{
T1 v3;
T2 v4;
};
template<class T>
class C: public B<T, T>{
T v5;
};
int main(){
B<int, double> obj1; //实例化出:B(int v3; double v4;)、A(double v1; double v2;)
C<int> obj2; //实例化出:C(int v5)、B(int v3; int v4)、A(int v1; int v2;)
return 0;
}
#include
using namespace std;
template<class T1, class T2>
class A{
T1 v1;
T2 v2;
};
template<class T>
class B: public A<int, double>{
T v;
};
int main(){
B<char> obj1; //自动生成两个模板类:A、B
return 0;
}
#include
using namespace std;
class A{
int v1;
};
template<class T>
class B: public A{
//所有从B实例化得到的类,都以A为基类
T v;
};
int main(){
B<char> obj1;
return 0;
}
#include
using namespace std;
template<class T>
class A{
T v1;
int n;
};
class B: public A<int>{
double v;
};
int main(){
B obj1;
return 0;
}
void Fun1(){}
class A{};
class B{
public:
void Fun(){}
};
template<class T>
class Tmp1{
friend void Fun1();
friend class A;
friend void B::Fun();
};
//任何从Tmp1实例化来的类,都有以上三个友元
#include
#include
using namespace std;
template<class T1, class T2>
class Pair{
private:
T1 key;
T2 value;
public:
Pair(T1 k, T2 v):key(k), value(v){};
bool operator < (const Pair<T1, T2> & p) const;
template<class T3, class T4>
friend ostream & operator << (ostream & o, const Pair<T3, T4> & p);
};
template<class T1, class T2>
bool Pair<T1, T2>::operator < (const Pair<T1, T2> & p) const{
//"小"的意思就是关键字小
return key < p.key;
}
template<class T1, class T2>
ostream & operator << (ostream & o, const Pair<T1, T2> & p){
o << "(" << p.key << "," << p.value << ")";
return o;
}
int main(){
Pair<string, int> student("Tom", 29);
Pair<int, double> obj(12, 3.14);
cout << student << " " << obj;
return 0;
}
TIPS:任意从:
template<class T1, class T2>
ostream & operator << (ostream & o, const Pair<T1, T2> & p)
生成的函数,都是任意Pair模板类的友元
#include
using namespace std;
class A{
int v;
public:
A(int n):v(n){}
template<class T>
friend void Print(const T & p);
};
template<class T>
void Print(const T & p){
cout << p.v;
}
int main(){
A a(4);
Print(a);
return 0;
}
TIPS:所有从
template<class T>
void Print(const T & p)
生成的函数,都成为A的友元,但是自己写的函数:void Print(int a){ }
不会成为A的友元
#include
using namespace std;
template<class T>
class B{
T v;
public:
B(T n):v(n){}
template<class T2>
friend class A;
};
template<class T>
class A{
public:
void Func(){
B<int> o(10);
cout << o.v <<endl;
}
};
int main(){
A<double> a;
a.Func();
return 0;
}
TIPS:A类,成了B类的友元。任何从A模板实例化出来的类,都是任何B实例化出来的类的友元
#include
using namespace std;
template<class T>
class A{
private:
static int count;
public:
A(){count++;}
~A(){count--;};
A(A &){count++;}
static void PrintCount(){cout << count <<endl;}
};
template<> int A<int>::count = 0; //声明静态成员,顺便初始化,也可以不初始化
template<> int A<double>::count = 0;
int main(){
A<int> ia;
A<double> da;
ia.PrintCount();
da.PrintCount();
return 0;
}