c++入门教程

01 绪论

两者之间的联系:
C++是C的超集。
支持C++语言的编译器必定支持C语言。

两者之间的不同:
C语言支持面向过程的结构化设计方法。(fortran)
C++语言支持面向对象的设计方法。
标准模板库STL使C++语言更加方便使用,并支持泛型程序设计方法。(Java,python)

面向过程(结构化程序设计方法)

自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。

自顶向下,逐步求精,模块化。

面向对象

面向对象方法是一种运用对象、类、封装、继承、多态等概念来构造、测试、重构软件的方法。

程序模块间的关系更为简单,程序模块的独立性、数据的安全性就有了良好的保障。
通过继承与多态性,可以大大提高程序的可重用性,使得软件的开发和维护都更为方便。

对象:属性(数据),行为(函数)

类:抽象

它将数据和操作数据的过程(函数)绑在一起,形成一个相互依存、不可分离的整体(即对象)

模块的独立性高、
代码的可重用性高;
可扩展性好、
可维护性好

同类对象中的数据原则上只能用本类提供的方法(成员函数)进行处理。类通过封装将接口与实现分离开来,通过接口与外界联系。对象之间通过消息进行通信。

具有相同属性和服务的一组对象的集合
为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。

封装(private,public)

涵义1:把对象的全部属性和全部服务结合在一起,形成一个不可分割的独立单位(即对象)
涵义2:信息隐蔽。即隐蔽对象的内部细节,对外形成一个边界,只保留有限的对外接口与外部联系。

封装的优点:
1.降低部件间的耦合度,提高部件的独立性
2.具有隐藏性和安全性 (如银行的帐户)
3.易于维护(由于数据独立,易于发现问题)
4.封装将对象的使用者与设计者分开,使用者只需要通过接口访问对象,不必了解对象的内部细节.提高了软件复用。
封装的缺点:
需要更多的输入输出函数。

继承

继承:类之间的相交关系,使得某类对象可以继承另外一类对象的特征和功能。
类间具有继承关系的特性:
类间具有共享特征(包括数据和程序代码的共享):遗传
类间具有细微差别或新增部分(包括非共享的程序代码和数据):变异
类间具有层次结构(如同人类通过继承构成了家族关系一样)

有效地提高了程序的可重用性,减小了程序代码的冗余度。
增强了程序的一致性,减少了程序模块间的接口和界面,使程序更易维护。
继承是自动传播程序代码的有力工具。
继承是新类构造、建立和扩充的有效手段。
继承具有传递性
如果类C继承类B,类B继承类A,则类C继承类A

多态

强制多态(强制类型转换)

重载多态(函数重载,运算符重载)

包含多态(虚函数)

类型参数化多态(函数模板,类模板)

多态是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。

02 简单程序设计

变量存储类型

动态临时变量

属于一时性存储,其存储空间可以被若干变量多次覆盖使用。

auto

默认(通常省略)

(C++新标准中,表示编译器自动推导的类型)
static(静态变量)
在内存中是以固定地址存放的,在整个程序运行期间都有效。
register
存放在通用寄存器中。
extern
使用另一个文件中定义的变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwFSWDqM-1631360818264)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201226172236982.png)]

C++中使用new和delete申请和释放内存。
new和delete不是函数,而是运算符。
使用new申请内存空间可以是一个单元,也可以是一组单元。
delete运算符释放申请的内存空间。

char* char_ptr = new char;
int* int_array = new int[20];  
Mixed* m1 = new Mixed(1,1,2);
*char_ptr = ‘a’;

delete char_ptr;
delete[] int_array;
delete m1;
typedef语句

为一个已有的数据类型另外命名

typedef  已有类型名  新类型名表;
枚举类型
enum  枚举类型名  {常量列表};

枚举类型应用说明:

枚举元素具有默认值,它们依次为:
0,1,2,…
也可以在声明时另行指定枚举元素的值,如:

enum Weekday { SUN=7,MON=1,TUE,WED,THU,FRI,SAT };

枚举元素是常量,不能对它们赋值。
例如,不能写:SUN = 0;
将整数值赋给枚举变量时需要进行强制类型转换。

Weekday w = (Weekday)5;

枚举值可以进行关系运算。

< >= <= == !=

int count;
for (count = WIN; count <= CANCEL; count++) 
    //++是int型操作,隐式类型转换
    {   
    	result = (game_result)count;
     	// 显式类型转换
        if (result == omit)
        {   
            cout << "The game was cancelled\n";
        }
        else
        {   
            cout << "The game was played ";
            if (result == WIN)    cout << "and we won!";
            if (result == LOSE)   cout << "and we lost.";
            cout << "\n";
        }
    }

>> // 提取符
<< // 插入符
//文件读取

ifstream sifile("a.txt");

sifile >> ……
    
sifile.close();

ofstream sofile("b.txt");

sofile << ……;

sofile.close();

03函数

形参是被初始化的内部动态变量,
寿命和可见性仅限于函数内部

类型标识符  函数名(形式参数表)                      
{  
   语句序列
}

函数参数传递:传值,传指针,传引用

传递参数值:

传递参数值,也叫值传递。
在函数被调用时, 实参值被拷贝给形参值。
值传递属于单向传递,不能改变实参

传递参数指针

传递参数指针,也叫传指针、指针传递。
“指针传递”实际是传变量的地址值。
参数调用时,实参的地址被拷贝为形参的指针变量。
指针传递可以改变实参的值。

传递参数引用

“引用”(Reference)即“别名”,即是某对象的另一个名字,引用的主要用途是为了描述函数的参数和返回值。特别是用于运算符的重载。
语法形式

类型 &  引用名=变量名;

//两种写法都正确:
int &ri = i;
int& ri = i;

// 相当于ri是i的别名,改变ri或i都相当于改变同一个量

引用不是值,不占用额外存储内存空间

int i=9;
int& ir=i;

引用在声明时必须初始化

而且引用一旦被初始化后,就不能改为指向其他对象。

int& ir;//error

引用的初始值可以是一个变量或另一个引用,且类型一致。

	int i=9;
   	int& ir=i;
	int& ir1=ir;
	float f=2;
	int& ir3=f;  //error

可以有指针的引用,不能有引用的指针

int  b=1;
int* p=&b;
(int*)& rp=p;  //rp是一个引用,它引用的是指针
(int&)* ra=a;  //error

不能建立数组的引用,可以建立数组元素的引用

int  a[10];
int& ra[10]=a;  //error
int&  ra=a[8];  //ok

传递参数引用,也叫传引用。
传引用实际是传变量的别名,形参是实参的别名。
参数调用时,实参和形参共享实参内存单元,不发生拷贝动作。
传引用可以改变实参的值。
传引用提高了函数执行效率。

例如:

void fun(int& x, int& y);

使用关键字inline 的函数称为内联函数。

编译时,在调用处用函数体进行替换

inline double calArea(double radius) 
{  
	return PI * radius * radius;
}
普通函数调用过程:

将返回地址压入栈
将参数压入栈
执行函数体
释放参数临时空间
回跳至函数调用位置

内联函数的作用:

在程序源文件中,保持功能的模块化、程序的结构化——函数
在程序的二进制可执行文件中(程序运行中)——去掉了函数模块,直接是语句块了

内联函数编译时,在调用处用函数体进行替换。
减少了运行时函数调用的参数传递、控制转移等开销。
提高运行效率。

内联函数对函数的参数及返回值有明确定义,增强了代码的安全性。而宏定义没有。
内联函数的参数和返回值具有明确的类型标识,宏定义没有。

关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。

函数体相对简单、被频繁调用的函数适合定义为内联函数。
内联函数体内不能有循环语句和switch语句。
内联函数不能定义为递归函数。

编译器决定函数是否为内联函数。
inline是一种用于实现的关键词,而不是用来声明的关键词。
inline只是对编译器的请求(建议),编译器可以拒绝。
现代C++编译器对代码进行优化,非inline函数也可能被inline化处理。
内联是以代码膨胀为代价的。
内联函数发生改变,所有调用内联函数的文件均需重新编译。

缺省形参值

函数在声明时可以预先给出缺省的形参值,调用时如给出实参——则采用实参值,否则采用预先给出的缺省形参值。

有缺省参数的形参必须在形参列表的最后,也就是说缺省形参值的右面不能有无缺省值的参数。因为调用时实参与形参的结合是从左向右的顺序。

int add(int x, int y = 5, int z = 6);//正确
int add(int x = 1, int y = 5, int z);//错误
int add(int x = 1, int y, int z = 6);//错误

如果一个函数有原型声明,且原型声明在定义之前,则缺省形参值必须在函数原型声明中给出。

已有函数声明,在定义时不再指定缺省形参值。

而如果只有函数的定义,或函数定义在前,则缺省形参值需在函数定义中给出。

在相同的作用域内,默认形参值的说明应保持唯一,但如果在不同的作用域内,允许说明不同的默认形参。

重载函数

C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。

//形参类型不同
int add(int x, int y);
float add(float x, float y);

//形参个数不同
int add(int x, int y);
int add(int x, int y, int z);

单接口、多实现

在C++中,可以为两个或多个函数提供相同的名字,并且功能相同,只要每个函数的参数表唯一就行(参数个数、参数类型、或参数顺序上有所不同)。
编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。

不能仅仅以不同返回类型,形参名区分重载函数

在重载函数参数存在隐式类型转换情况下,重载函数在调用时存在二义性。
这时必须使用显式强制转换完成函数调用。

#include <iostream>
void print(double a) {
	cout << "print_d  " << a << endl;
}
void print(long a) {
	cout << "print_l " << a << endl;
}
int main(void) {
    int a;
    print(a); //error
    print(double(a));
    print(long(a));
    return 0;
}

既作为重载函数,又作为有默认参数的函数时,有时会发生二义性。

int maximum(int x, int y, int z = 10) 
{   
    int max = x;
    if (y > max) max = y;
    if (z > max) max = z;
    return max;
}
int maximum(int x, int y) 
{    
    int max = x;
    if (y > max) max = y;
    return max;
}

//调用如下,会产生二义性
maximum(a,b);

04 类和对象

① 类声明中的访问限定符private、public、protected没有先后次序之分 .
② 在一个类中,访问限定符private、public、protected的出现次数没有限制
③ 数据成员和函数成员都可以设置为public、private或protected属性 。
出于信息隐藏的目的,常将数据成员设置为private权限
将需要让类的外部函数(非本类定义的函数)访问的成员函数设置为public权限。

④如果没有明确写出访问限定符,则默认成员具有private的访问权限。
⑤数据成员可以是任何数据类型
如整型、浮点型、字符型、数组、指针、引用等
也可以是另外一个类的对象或指向对象的指针
还可以是指向自身类的指针
但不能是自身类的对象。
此外,数据成员不能指定为**自动(auto)、寄存器(register)和外部(extern)**存储类型。

class B {
private:
int a;
A  obja1;
A  *obja2;
B  *objb;
B  b1;	// X
auto b;		// X
extern int c;	// X
register int d;	// X
public:
……
};
auto有两个含义

声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

函数实现在类声明外
<类型>  <类名> ::<函数名>(<参数表>)
{<函数体>}
内联函数

为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
作用是:减少实际函数调用的次数,减少函数调用的开销
内联函数体中不要有复杂结构(如循环语句和switch语句)。
在类中声明内联成员函数的方式:将函数体放在类的声明中,使用inline关键字。

class Point
{
public:
    void Init(int initX,int initY);
    int GetX();
    int GetY();
private:
    int X, Y;
};
inline void Point::Init(int initX,int initY)
{ 
    X = initX;
 	Y = initY;
}
inline int Point::GetX()
{  
    return X;
}
inline int Point::GetY()
{  
    return Y;
}

缺省形参值

类的成员函数也可以缺省形参值,调用规则与普通函数相同。
类成员函数的默认值,一定要写在类声明中,不能写在类声明之外的函数实现中。

class Clock {
public:
void setTime(int newH = 0, int newM = 0, int newS = 0);
...
};
this指针

this是成员函数中,指向当前对象自身(即成员函数所属的类对象的首地址)的隐含指针.

class X
{		……
		f(…)
};
		
X::f (……)
{
		this->member
}
编译器对this指针的处理

① 编译器改变类成员的定义,用额外的this指针重新定义每个类成员函数

② 编译器改变每个类成员函数的调用,加上一个额外的实参,即被调用对象的地址

this指针的两种常见应用

使用this指针区分二义性

使用this指针返回调用对象

Clock& Clock::SetHour(int NewH)
{
    Hour = NewH;
	return *this;
}
private

外界不能访问
在类内部可以被任意访问

类内部就是“内定义范围之内”
包括:花括号{ }; 类和类限定运算符内部

在类外,只允许间接地访问。只能通过对象的接口--具有公有属性的成员函数来访问

protect

只有一个类时,访问权限同private
类内部可以任意被访问
在类外,只允许间接地访问。只能通过对象的接口--具有公有属性的成员函数来访问
在派生类中,具有私有的或者保护的访问属性
区别于private成分在派生类中是不可访问的

private和protected异同

在一个单独的类中,private与protected类型的数据都只能在类定义中被直接访问;在类定义之外, private与protected类型的数据都不能被直接访问
在有继承关系的类族中,父类的private数据在子类中不能被直接访问;而父类的protected数据可以在子类中被直接访问

对象与类的关系

对象和类的关系就是变量与变量类型的关系,对象是类的一个实例。

指向对象的指针
Clock clock1;
Clock *ptr=&clock1;

Clock *ptr2 = new Clock;
…
delete ptr2;

Clock clock[10];	// 指针指向对象数组
Clock *clock1 = new Clock[10];
…
delete[] clock1;

Clock * clock[10];	// 对象的指针数组
for(int i=0;i<10;i++)
	clock[i] = new Clock;
…
for(int i=0;i<10;i++)
	delete clock[i];
对象赋值

两个对象必须类型相同;
进行数据成员的值拷贝,赋值之后,两不相干;
若对象有指针数据成员,赋值可能产生问题;(浅拷贝出现问题,必须使用深拷贝)

05 类和对象2

构造函数
class X{
    ……
    X(…);`// 构造函数
    ……
} 

构造函数与类同名。
构造函数没有返回类型。
构造函数可以被重载。
构造函数由系统自动调用,不允许在程序中显式调用。

只能在定义对象时,由系统自动调用!
调用形式:类名 对象名(参数表);
系统将根据参数表调用某个构造函数
若无参数表将调用缺省构造函数。

不允许程序员在程序中显示调用构造函数的名称,任何时候都不允许!

① 构造函数不能有返回类型,即使void也不行。

②构造函数由系统自动调用,不能在程序中显式调用构造函数。

Person wang;		//调用构造函数
Wang.Person(“WangWei”,20);	//error

③ 构造函数的调用时机是定义对象之后的第一时间,即构造函数是对象第一个被调用的函数。

④ 定义对象数组或用new创建动态对象时,也要调用构造函数。此时不需要有变量名,但定义数组对象时,通常必须有不需要参数的构造函数

⑤ 构造函数通常应定义为公有成员,因为在程序中定义对象时,要涉及构造函数的调用,尽管是由编译系统进行的隐式调用,但也是在类外进行的成员函数访问。

无参构造

1.系统默认构造函数
C++规定,每个类必须有构造函数,如果一个类没有定义任何构造函数,编译器将会为它生成一个默认构造函数。

class X {
    X(){}    //系统默认构造函数类似于此
    ……
}

在用默认构造函数创建对象时,如果创建的类是全局对象或静态对象,则对象所有数据成员初始化为0;如果创建的是局部对象,即不进行对象数据成员的初始化。

point p1;	// 全局
int main() 
{  
    static point p2;	//静态局部
	point p3;	局部
    return 0;
}

在类没有定义任何构造函数时,系统才会产生默认构造函数。
一旦定义了任何形式的构造函数,系统就不再产生默认构造函数。

2.重载的无参数构造函数

系统生成的默认无参数构造函数,并未对对象的数据成员作什么实际的初始化工作。
C++允许显式定义无参数的构造函数,这样就能通过它为对象的数据成员提供初始值。
有时为了让类能够正常工作,必须显示提供无参构造函数。

缺省形参构造函数

在数据成员的取值比较固定时,可以通过为构造函数的参数提供缺省值初始化它们。

重载构造函数

利用缺省形参值的构造函数,将上面的几个构造函数结合成一个

会自动进行强制类型转化

拷贝构造函数
class X{
public:
    ……
    X(const X&);     //拷贝构造函数的常见原型
}

当一个类有指针类型的数据成员时,默认拷贝构造函数常会产生指针悬挂问题 。

(1)拷贝构造函数与一般构造函数相同,与类同名,没有返回类型。
(2)拷贝构造函数的参数常常是const类型本类对象的引用。
(3)在多数情况下,默认拷贝构造函数能够完成对象的复制创建工作,但当类具有指针类型的数据成员时,默认拷贝构造函数就可能产生指针悬挂问题,需要提供显式的拷贝构造函数。
(4)对拷贝构造函数的调用常在类的外部进行,应该将它指定为类的公有成员。

拷贝函数调用

情况1:调用拷贝构造函数

情况2:以对象作函数参数时调用拷贝构造函数

情况3:以对象作为函数返回值时调用拷贝构造函数

解决指针悬挂问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ti7QWpnc-1631360818273)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201218175105355.png)]

Person::Person(const Person &p) 
{  
    name = new char[strlen(p.name) + 1];
	strcpy_s(name, strlen(p.name)+1, p.name);
	age = p.age;
}
//先分配一块新的空间,再把字符串中的内容复制过去
初始化列表
构造函数名(参数表):成员1(初始值1),成员2(初始值2),…
{
……
}

介于参数表后面的":"与函数体{…}之间的内容就是成员初始化列表。其含义是将括号中的初始值参数的值赋给该括号前面的成员

① 构造函数初始化列表中的成员初始化次序与它们在类中的声明次序相同,与初始列表中的次序无关。如对例3-10中的类而言,下面3个构造函数是完全相同的。

Tdate::Tdate(int m,int d,int y):month(m),day(d),year(y){}
Tdate::Tdate(int m,int d,int y):year(y),month(m),day(d){}
Tdate::Tdate(int m,int d,int y):day(d),year(y),month(m){}

尽管三个构造函数初始化列表中的month、day和year的次序不同,但它们都是按照month→day→year的次序初始化的,这个次序是其在Tdate中的声明次序。

② 构造函数初始化列表先于构造函数体中的语句执行。
③ 常量成员,引用成员,类对象成员,派生类构造函数对基类构造函数的调用必须采用初始化列表进行初始化。

析构函数
class X
{
	~X ( ) {……};
}

函数名为 ~类名
无参数
无返回值
不能重载:每个类仅有一个析构函数

析构函数调用时机

对象生命期结束时自动调用
如:Point* p = new Point;
delete p;
局部对象:定义的语句块结束处
{

​ Point p;
……
}
全局对象:程序结束时
静态对象:程序结束时

① 若有多个对象同时结束生存期,C++将按照与调用构造函数相反的次序调用析构函数。
② 每个类都应该有一个析构函数,如果没有显式定义析构函数。C++将产生一个最小化的默认析构函数。
③ 构造函数和析构函数都可以是inline函数
④ 在通常情况下,析构函数与构造函数都应该被设置为类的公有成员,虽然它们都只能被系统自动调用的,但这些调用都是在类的外部进行的。

06类与对象3

每次创造对象:先构造子对象,最后构造成员变量。

不仅要负责对本类中的基本类型成员数据赋初值,也要对子对象成员初始化。

子对象的构造必须使用初始化列表。

类名::类名(子对象成员所需的形参,本类成员形参):子对象1(参数),子对象2(参数),......
{  
           本类初始化  
}

①组合类构造函数调用顺序:先调用子对象的构造函数(按组合时的声明顺序,先声明者先构造)。然后调用本类的构造函数。

②析构函数的调用顺序相反。

③组合类构造函数初始化列表中未出现的子对象,用子对象的默认构造函数(无参构造函数)初始化。

④系统自动生成的组合类默认构造函数中,子对象全部用子对象的默认构造函数(无参构造函数)初始化。

Line::Line(Point xp1, Point xp2): p1(xp1),p2(xp2)
{  
    cout << “Calling constructor of Line” << endl;
	double x = (double)p1.getX() - (double)p2.getX();
	double y = (double)p1.getY() - (double)p2.getY();
	len = sqrt(x*x + y*y);
}
Line::Line(Point xp1, Point xp2): p1(xp1),p2(xp2)
{  
    cout << “Calling constructor of Line” << endl;
	double x = (double)p1.getX() - (double)p2.getX();
	double y = (double)p1.getY() - (double)p2.getY();
	len = sqrt(x*x + y*y);
}

int main()
{  
    Point myp1(1, 1), myp2(4, 5);
	Line line(myp1, myp2);
	return 0;
}

运行结果

Calling the copy constructor of Point 拷贝构造形参xp1
Calling the copy constructor of Point 拷贝构造形参xp2
Calling the copy constructor of Point 拷贝构造p1
Calling the copy constructor of Point 拷贝构造p2
Calling constructor of Line 构造函数

Line::Line(Line& l): p1(l.p1), p2(l.p2)
{  
    cout << “Calling copy constructor of Line” << endl;
	len = l.len;
}

运行结果

Calling the copy constructor of Point 拷贝构造line2.p1
Calling the copy constructor of Point 拷贝构造line2.p2
Calling copy constructor of Line 拷贝构造line2

组合类的构造函数
//组合类的构造函数
Line::Line(int x1,int y1,int x2,int y2): p1(x1,y1),p2(x2,y2)
{  cout << “Calling constructor of Line” << endl;
double x = (double)p1.getX() - (double)p2.getX();
double y = (double)p1.getY() - (double)p2.getY();
len = sqrt(x*x + y*y);
}

int main()
{  int myx1(1), myy1(1),myx2(4), myy2(5);
Line line(myx1,myy1,myx2,myy2);
return 0;
}

运行结果

Calling constructor of Line 新建两个点,然后构造直线

子对象中的函数可以直接访问

void Line::setP1(int xx, int yy)
{  
    p1.setX(xx);
	p1.setY(yy);
}
前向引用声明

类应该先声明,后使用
如果需要在某个类的声明之前,引用该类,则应进行前向引用声明:
class B;
前向引用声明只为程序引入一个标识符,但具体声明在其他地方。

// X
class A
{public:
   void f(B b); 
};
class B
{public:
   void g(A a);
};

// √
class B; //前向引用声明
class A
{
public:
   void f(B b);	//只能有定义,具体函数体需要在类B定义之后
};
class B
{
public:
   void g(A a);
};

void A::f(B b)
{ 
    m_a = b.get();
}

对于类B的实际使用,要在类B的完整声明(或定义)之后。

尽管使用了前向引用声明,但是在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。

class Fred; 		//前向引用声明
class Barney
{   
    Fred x; //类Fred的声明暂不完善		
    Fred &x; 	//经过前向引用声明,可以声明Fred类对象的引用和指针。
    x.method(); 	//X,不能调用Fred的成员函数
};

仅有类Fred前向声明、没有定义类Fred。

不能声明Fred的对象

不能使用Fred的对象(调用Fred成员函数)。

只能声明Fred的函数形参,引用和指针。

07数据的共享与保护

作用域

从小到大

①函数原型 (仅限于函数原型的声明)

其作用域始于“(”,结束于“)”。

形参 的作用域仅在()之间,不能用于程序正文其他地方,因而可有可无。

②块作用域(局部作用域)

块是一对花括号括起来的程序单元。

在块中声明的标识符,其作用域从声明处开始,直到块结束的右花括号。

函数的形参,在块中声明的标识符,具有块(局部)作用域。其作用域自声明处起,限于函数、块中。

③类作用域

如果在X的成员函数中没有声明同名的局部作用域标识符,那么在该函数内可以访问成员m.
通过表达式x.m或者X::m访问。x::m是静态成员或者静态函数访问的方式
通过表达式ptr->m

④命名空间namespace

全局命名空间:默认的命名空间
匿名命名空间:对每个源文件是唯一的

一个命名空间确定了一个命名空间作用域
使用其它命名空间作用域中的标识符
命名空间名::标识符名
例:声明一个SomeClass型的对象

SomeNs::SomeClass obj1;

将其它命名空间作用域的标识符暴露于当前作用域

对所有标识符:
using namespace 命名空间名;

对特定标识符:
using 命名空间名::标识符名;

⑤文件作用域

不在前述定义域中定义的标志符
也被称为:全局变量
在整个文件中都可以使用

可见性

标志符在其作用域内,并非总是可见的
因为作用域嵌套,如果在内层作用域内定义了同名变量,则在内层将不可见外层的同名变量

标识符应声明在先,引用在后。
如果某个标识符在外层中声明,且在内层中没有同名标识符的声明,则该标识符在内层可见。
对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。

在同一作用域内的对象名、函数名、枚举常量名会隐藏同名的对象名、类名或枚举类型名。
重载的函数可以有相同的函数名。

使用作用域限定符 ::

在内层使用本来不可见的全局变量

生存期
静态生存期:

全局变量(变量定义->程序结束)

static变量(变量定义->程序结束) (程序模块化与数据共享)

动态生存期

块作用域中声明的,没有用static修饰的对象是动态生存期的对象(习惯称局部生存期对象)。
开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。

静态成员
静态数据成员

用关键字static声明
该类的所有对象维护该成员的同一个拷贝
必须在类外定义和初始化,用(::)来指明所属的类。

静态数据成员不是由构造函数创建的
是由变量定义语句创建的:

static int count;
int Point::count = 0;

类的每个对象都独立拥有自己的数据成员;
但整个类只拥有一份静态数据成员
(整个类也只拥有一份成员函数)

静态成员是类的所有对象共享的
(成员函数也是所有对象共享的)

静态成员函数

类外代码可以使用类名和作用域操作符来调用静态成员函数。
没有this指针
静态成员函数只能引用属于该类的静态数据成员或静态成员函数。

声明时使用关键字static,定义时不使用关键字

对public的静态成员的访问:
可以通过类 Point::showCount();也可以通过任何一个对象 a.showCount();

对普通成员的访问需要当前对象(*this)
对静态成员的访问不需要当前对象

static成员函数 与 普通(非static)成员函数 的 不同:
普通成员函数有 this 指针
static成员函数 没有 this 指针

class Point
{public:
Point(int xx=0,int yy=0):x(xx),y(yy) 
{ count++; }	// 新建一个点,计数器自增
Point(Point &p);
int getX() 
{  return x; }
int getY() 
{ return y; }
void showCount()
{  cout<<" Object count="<<count<<endl; }
private:
   int x, y;
static int count;	// 声明
};
Point::Point(Point &p)
{  x = p.x;
y = p.y;
count++;	// 新建一个点,计数器自增
}
int Point::count = 0;	//类外初始化

对于非static成员函数,编译器改变其调用,加上一个额外的实参,即被调用对象的地址。

对static成员函数的调用,则不增加这个额外的实参。(static函数本来就没有this参数)

静态成员函数中没有当前对象,所以没有this指针

结论:

静态成员函数对非静态数据成员的访问要通过对象

对静态成员的访问不需要通过对象

class A
{
    public:
	static void f(A a);
	private:
	int x;
};
void A::f(A a)
{  
    cout << x;	//X
	cout << a.x;	//√
}
友元函数

友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和 protected成员
作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。

一个类的友元函数能够直接访问该类所有成员,包括public、protected、private类型的成员。

友元函数不用反复调用类的成员函数,效率高。

class X
{
    ……
    friend  T  f(…);         //声明f为X类的友元函数
    ……
};
……
T  f(…) { …… }          //友元不是类成员函数,定义时不能用"X::f"限定函数名
friend float dist(Point& a, Point& b);	// 类内声明

float dist(Point& a, Point& b) // 定义
{  
    double x = a.x - b.x;
	double y = a.y - b.y;
	return static_cast<float>(sqrt(x*x+y*y));
}
友元类

若一个类为另一个类的友元,则此类的所有成员函数都能访问对方类的私有或保护成员。
声明语法:将友元类名在另一个类中使用friend修饰说明。

class A
{ 
    friend class B;
public:
  void display()
  {  
      cout<<x<<endl;
  }
private:
  int x;
}



class B
{
public:
  void set(A& a,int i);	// 类A中对象a为函数形参
  void display(const A& a);
};
void B::set(A& a,int i)
{   
    a.x=i;	// 直接访问a的私有数据
}
void B::display(const A& a)
{	
    cout<<a.x<<endl;
}

// 一个B可以有多个A, B可以直接访问A的私有,保护成员,A和B是两个独立的类。

// OR

class B
{
public:
  void set(int i);
  void display();
private:
  A a;	// 类A中对象a直接为私有成员
};
void B::set(int i)
{   
    a.x=i;	// 直接访问a的私有数据
}
void B::display()
{   
    cout<<a.x<<endl;
}

// 在这个设计中,A是B的子对象,一个B有且只能有一个A
友元关系的特征
单向的

如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。

不传递的

如果声明C类是B类的友元,并且B类是A类的友元,并不能认为,C类是A类的友元。

不继承的

如果声明B类是A类的友元,并且C类是A类的子类,并不能认为,B类是C类的友元。

只要没有“friend class B;”这句声明,就没有友元关系

共享数据的保护

常类型的对象必须进行初始化,而且不能被更新。

常对象:必须进行初始化,不能被更新。

const 类名 对象名 
类名 const 对象名 

常引用:被引用的对象不能被更新。

const  类型说明符  & 引用名

常数组:数组元素不能被更新。

类型说明符  const  数组名[大小] = {初值列表};

常指针:指向常量的指针

const 类型说明符 * 指针名 = &常量;  	//指向常量的指针
const 类型说明符 * const 指针名 = &常量; //指向常量的常指针

常数据成员
使用const说明的数据成员。
常成员函数
使用const关键字说明的函数——是不更新成员数据的成员函数。

常数据成员

常对象只能调用常成员函数。
普通(“非常”)对象调用普通 (“非常”)函数;
普通对象也能调用常函数。

常数据成员以初始化列表的形式初始化

#include <iostream>
using namespace std;
class A
{public:
   A(int i);
void print() const;
private:
const int a;
static const int b;
};

const int A::b = 10; 	// 静态变量初始化
A::A(int i):a(i)	// 常数据成员以初始化列表的形式初始化,不能通过=赋值
{ }

常成员函数

常成员函数不更新对象的数据成员。

说明格式

类型说明符  函数名(参数表)const;

这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。

const可以用于函数重载

const函数传递的是const this指针

普通成员函数传递的是(变量)this指针

常成员函数只能调用常成员函数

常引用作形参 const &
类型说明符  函数名(const 类型&);

作用:
提升函数调用时传参的效率——传引用&
不允许函数改变实参——保护实参

通过临时变量储存结果,不改变传入的东西,最后返回临时变量。

多文件结构和编译预处理命令

#include 包含指令
将一个源文件嵌入到当前源文件中该点处。
#include<文件名>
按标准方式搜索,文件位于C++系统目录的include子目录下
#include"文件名"
首先在当前目录中搜索,若没有,再按标准方式搜索。
#define 宏定义指令
定义符号常量,很多情况下已被const定义语句取代。
定义带参数宏,已被内联函数取代。
#undef
删除由#define定义的宏,使之不再起作用。

#if 常量表达式1
    程序正文1  //当“ 常量表达式1”非零时编译
#elif 常量表达式2
    程序正文2  //当“ 常量表达式2”非零时编译
#else
    程序正文3  //其他情况下编译
#endif
        
#ifndef 标识符
   程序段1
#else
   程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。
        

09 类的继承与派生

保持已有类的特性而构造新类的过程称为继承。
在已有类的基础上新增自己的特性而产生新类的过程称为派生。
被继承的已有类称为基类(或父类,超类)。
派生出的新类称为派生类(或子类) 。

继承的目的:实现代码重用。

以存在的类为基础定义新的类,新类即拥有基类的数据成员和成员函数。

继承目的
代码重用
描述能力:类属关系广泛存在

派生类的定义

继承方式:private,protected,public

派生类中新增加的成分:数据成员,成员函数

class 派生类名:继承方式  基类名
{
        成员声明;
};
派生类的生成过程
  1. 吸收基类成员
  2. 改造基类成员
    改变基类成员在派生类中的访问属性
    重定义基类函数(函数名相同,参数名相同)
    重载基类函数(函数名相同,参数名不同)
  3. 添加新的成员
派生类不能继承基类以下内容

基类的构造函数和析构函数
基类的友元函数
静态数据成员和静态成员函数

内存布局

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpeKMEpt-1631360818277)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201221170127896.png)]

继承方式

公有,私有,保护

不同继承方式会不同程度地影响基类成员在派生类中的访问权限。
class的默认继承方式是私有继承

保护成员

基类中protected的成员
类内部:可以访问
类的使用者:不能访问
类的派生类成员:可以访问

protected 成员的特点与作用
对建立它的类,派生类来说,它与 public 成员的性质相同。
对于类的使用者来说,它与 private 成员的性质相同。
既实现了数据隐藏,又方便继承,实现代码重用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lY6BGuC5-1631360818280)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201221203028421.png)]

公有继承

基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。

派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

在main()函数中通过派生类的对象只能访问基类的public成员

私有继承

派生类中能访问public和protected成员

main()中不能访问基类中public成员

保护继承

基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问。
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
通过派生类的对象不能直接访问基类中的任何成员

类型兼容规则(只适用于公有继承)

1.派生类的对象可以赋值给基类对象。

Derived d;
Base b = d;

2.把派生类对象的地址赋值给基类指针。

Derived d;
Base *b = &d;

3.用派生类对象初始化基类对象的引用。

Derived d;
Base &b = d;

在派生类的构造函数中,初始化可以用基类的构造函数。

不论以哪种方式把派生类对象赋值给基类对象,都只能访问到派生类对象中的基类对象部分的成员,不能访问派生类的自定义成员。

多继承
class 派生类名:继承方式1 基类名1,继承方式2 基类名2, ...
{
        成员声明;
};
继承时的构造函数

基类的构造函数不被继承,派生类中需要声明自己的构造函数。

定义构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成(默认)。

C::C()
//等同于
C::C():B()

(使用初始化列表自动调用基类构造函数)

派生类的构造函数需要给基类的构造函数传递参数

派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表)
{
	本类成员初始化赋值语句;
};

当基类中声明有缺省构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数,也可以不声明。构造派生类的对象

时,基类的缺省构造函数将被调用。

当需要执行基类中带形参的构造函数来初始化基类数据时,派生类构造函数应在初始化列表中为基类构造函数提供参数。

派生类名::派生类名(形参表):基类名1(参数),基类名2(参数), ...基类名n(参数),
						内嵌对象的初始化
{
        本类成员初始化赋值语句;
};

1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。

2. 对内嵌对象进行初始化,初始化顺序按照它们在类中声明的顺序。

3.执行派生类的构造函数体中的内容。

先祖先(基类)
再客人(成员对象)
后自己

class Derived: public Base2,public Base1,public Base3
{public:
Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b)
{ }
private:    //派生类的内嵌成员对象
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

base2 base1 base3 member2 member1 member2 member3

若建立派生类对象时没有编写拷贝构造函数,编译器会生成一个隐含的拷贝构造函数。

该函数先调用基类的拷贝构造函数,再为派生类新增的成员对象执行拷贝。
若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。

例如:

C::C(const C &c1): B(c1) {…}

析构函数也不被继承,派生类自行声明
声明方法与一般(无继承关系时)类的析构函数相同。
不需要显式地调用基类的析构函数,系统会自动隐式调用。
析构函数的调用次序与构造函数相反。

P68-P73

派生类成员的标识与访问

当派生类与基类中有相同成员时:
若未强行指明,则通过派生类对象使用的是派生类中的同名成员。

d.Show();   =    d.Derived::Show();

如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名限定。
d.Base::Show();

如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名限定。

d.Base::Show();

在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性

解决方式:虚基类

解决方法一:用类名来限定 c1.A::f() 或 c1.B::f()
解决方法二:同名隐藏 在C 中声明一个同名成员函数f(),f()再根据需要调用 A::f() 或 B::f()

虚基类

虚基类的引入
用于有共同基类的场合
声明
以virtual修饰说明基类 例:class B1:virtual public B
作用
主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.
为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝
注意:
在第一级继承时就要将共同基类设计为虚基类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYOMqcRI-1631360818283)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201222210343155.png)]

在最远派生类C中,只有来自类B的一份拷贝

C obj;
obj.b; //b为B中protected成员

建立对象时所指定的类称为最(远)派生类。

虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。

在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未

列出,则表示调用该虚基类的默认构造函数

在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。

Derived(int var) : Base0(var), Base1(var), Base2(var)

在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略

11 虚函数

虚函数重定义时,函数原型要完全一致,包括:返回类型,函数名、参数个数、参数类型、顺序必须与原函数完全一致

即:基类和公有派生类中都拥有的用virtual修饰的原型一样的函数,是虚函数。

区分虚函数与函数重载(形参,返回值不同)

virtual可以只修饰基类的函数,而子类中的同型函数前可以省略virtual关键字。这样的函数族依然是虚函数

即:当派生类重定义虚函数时,无论是否使用了virtual 关键字,该成员函数都将是虚函数

有virtual函数类族的析构函数一定是虚析构函数

声明:virtual <类型> 函数名(参数表);

类外定义:可以不写关键字virtual了,虚函数的形参默认值要写在基类中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8UDRMiPX-1631360818284)(C:\Users\DELL\Desktop\捕获.PNG)]

通过基类的指针、引用调用子类虚函数;
通过基类对象名不能调用到子类虚函数

引用一旦赋值不能改变

通过变量的引用调用虚函数有缺陷:
引用一旦初始化后,就无法重新赋值

一般方法

通过函数调用,函数形参是引用

若某类中定义有虚函数,则其析构函数也应当说明为虚函数

若基类的析构函数是虚函数,则其派生类的析构函数也是虚函数

构造时先构造基类,再构造派生类。析构时相反。

纯虚函数
class 类名
{…
        virtual 返回值类型 函数名(参数表) = 0;
…
 };

纯虚函数在基类中申明后,不能在基类中定义函数体
纯虚函数的具体实现只能在派生类中完成
纯虚函数是为了实现多态性而存在的
纯虚函数不实现,只定义为0。

如果一个类中至少有一个纯虚函数,那么这个类成为抽象类。

不能申明抽象类对象。

只作为基类被继承,无派生类的抽象类毫无意义。

抽象类对象里的函数在抽象类中被申明,但在子类中才能使用。

可以定义指向抽象类的指针和引用,它们必然指向或引用派生类对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7qsPVfBZ-1631360818285)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20201218092503790.png)]

p1指向派生类, new cat后不用加上变量名, 纯虚函数只能在子类中被调用。

12 运算符重载

“运算符重载”是针对C++中原有运算符进行的,不可能通过重载创造出新的运算符。
可以重载几乎所有已知的运算符
除5个运算符外: . .* :: ?: sizeof
其他运算符都可以重载。
不得为重载的运算符函数设置默认值,调用时也就不得省略实参。
除了new和delete这两个较为特殊运算符以外,任何运算符如果作为成员函数重载时,不得重载为静态函数。

=、[]、()、->以及所有的类型转换运算符只能作为成员函数重载,且不能是针对枚举类型操作数的重载。
假定已经作为某个类的成员函数重载了二元运算符+,且c1、c2都是该类的对象,则 c1.operator+(c2)与c1+c2含义相同。
如果+作为该类的非成员函数重载,则operator+(c1,c2)与c1+c2含义相同。
重载的运算符保持其原有的操作数个数、优先级和结合性不变。
语义要与已知功能保持一致
比如:不能把“+”重载成“-”

函数类型 operator 运算符(形参表) // operator与运算符中间无空格
{
    函数体;
}

成员函数:

+,-,前置++,后置++

对象自身是一个操作数,形参表中的参数个数要比运算目数少1

operator+(c1,c2)与c1+c2

clock operator+(const clock &c) const;

clock clock::operator+(const clock &c) const // 需要和函数声明一模一样
{
    clock temp;
    temp.hour = hour+c.hour;
    temp.min = min+c.min;
    temp.second = second+c.second;
    temp.format();
    return temp;
}

clock operator-(const clock &c)
{
    int temp1 = hour*3600+min*60+second;
    int temp2 = c.hour*3600+c.min*60+c.second;
    int ans = temp1-temp2;
    clock temp(0,0,ans);
    return temp;
}

clock& operator++() 
// ++c1, 传引用是因为函数返回的是一个临时变量,++(++c1)连用时不传引用时会出错
{
    second++;
    format();
    return *this;
}

clock operator++(int)
// c1++,后置++不存在连用,默认传int参数,对象old在函数返回时被析构,返回时无索引
{
    clock old(*this);
    second++;
    format();
    return old;
}

赋值函数只能是成员函数

类型转换函数只能是成员函数,函数定义时不需要返回值。

fraction& fraction::operator=(fraction f)
{
    num = f.num;
    den = f.den;
    return *this;
}

fraction::operator long()
{
    return num/den;
}

取下标

int& int_array::operator[](int index)
{
    if(index < 1 || index > size)
    {
        delete []data;
        exit(2);
    }
    return data[index-1];
}

字符串深拷贝

//运算= ,str=str2
STRING& STRING::operator=(const STRING &s)
{
    if(&s==this)	
		return *this;
	if(p)
		delete[]p;
	if(s.p==NULL)
		p=NULL;
	else
	{	p=new char[strlen(s.p)+1];	//new出一个新对象,然后copy
		strcpy_s(p, strlen(s.p) + 1,s.p);
	
	return *this;
}

对象old在函数返回时被析构,返回时无索引

友元函数:

形参表中的参数个数与运算目数相同

<< >>

输入,输出函数只能是友元函数

输出时c不会改变,所以const引用

friend ostream& operator<<(ostream &o, const clock &c);

ostream& operator<<(ostream &o, const clock &c) //函数定义时不需要加上friend
    // 函数定义中,函数类型 类名::函数名
{
    return o << c.hour << ":" c.minute << ":" << c.second;
}

输出时c会改变,所以直接加上引用

istream& operator>>(istream &i, clock &c)
{
    char ch;
    i >> c.hour >> ch >> c.min >> ch >> c.second;
    c.format() // 输入完直接格式化
    return i;
}

+,-同理

friend clock operator+(clock c1, clock c2);

clock operator+(clock c1, clock c2) // 友元函数不属于某个类中的,所以不需要加上定义域
{
    return clock(c1.hour+c2.hour,c1.min+c2.min,c1.second+c2.second); 
    // 不需要变量名,相当于强制类型转化
}

前置

改变第一操作数,传递的是引用。

返回的是引用

friend clock& operator++(clock &c);

clock& operator++(clock &c)
{
    c.second++;
    c.format();
    return c;
}

后置

改变第一操作数,传递的是引用

返回临时变量,为值

friend clock operator++(clock &c, int i)
{
    clock old(c);
    c.second++;
    c.format();
    return old;
}

friend clock operator++(clock &c, int i)
{
    c.second++;
    c.format();
    return clock(c.hour,c.min,c.second-1);
}

14 模板类

将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象

函数模板

函数模板格式

template  <typename T>
 类型名 函数名(参数表)
 {函数体的定义}

template  <class T>
 类型名 函数名(参数表)
 {函数体的定义}

其中,<模板形参表>可以包含基本数据类型,也可以包含类类型。类型形参需要加前缀typename 或class。如果类型形参多于一个,则每个类型形参都要使用前缀。<模板形参表>中的参数必须是惟一的,而且在<函数定义体>中至少出现一次。
函数模板定义不是一个实实在在的函数,编译系统不为其产生任何执行代码。该定义只是对函数的描述,表示它每次能单独处理在类型形式参数表中说明的数据类型。

函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行。

模板函数有一个特点,虽然模板参数T可以实例化成各种类型,但是采用模板参数T的各参数必须保持完全一致的类型。

克服了C++函数重载“用相同函数名字重写几个函数”的繁琐

template <typename T>
T abs(T x)
{  
    return x < 0 ? -x : x;
}

int main()
{
    int i = -20;
    int j = abs(i);         
    double k = -4.5;
    double t = abs(k);
    return 0;
}
函数模板的特化
template <> 
返回类型 函数名<特化的数据类型>(参数表) {}


template <>  //特化
const char* tmin<const char*>(const char *a,const char *b) 
{
	return (strcmp(a,b)<=0)?a:b;
}
类模板
template <模板参数表>
 class 类名
 { 类成员声明 };
     
// 类模板格式

 template <模板参数表>
 类型名 类名<T>::函数名(参数表)

     
// 类模板外定义成员函数
     
template <class T>    
class Node{ 
private:
   T item; 
public:
   T GetElem();  
};

template <class T>      
T Node<T>::GetElem() {  
   return item;    
}

15 STL

STL的核心内容包括容器、迭代器、算法三部分

STL的容器常被分为顺序容器、关联容器和容器适配器三类

C++提供的顺序类型容器有向量(vector)、链表(list)、双端队列(deque)。
关联容器主要包括集合(set)、多重集合(multiset),map,multiplemap
operator<<(ostream &o, const clock &c) //函数定义时不需要加上friend
// 函数定义中,函数类型 类名::函数名
{
return o << c.hour << “:” c.minute << “:” << c.second;
}




输出时c会改变,所以直接加上引用

```c++
istream& operator>>(istream &i, clock &c)
{
    char ch;
    i >> c.hour >> ch >> c.min >> ch >> c.second;
    c.format() // 输入完直接格式化
    return i;
}

+,-同理

friend clock operator+(clock c1, clock c2);

clock operator+(clock c1, clock c2) // 友元函数不属于某个类中的,所以不需要加上定义域
{
    return clock(c1.hour+c2.hour,c1.min+c2.min,c1.second+c2.second); 
    // 不需要变量名,相当于强制类型转化
}

前置

改变第一操作数,传递的是引用。

返回的是引用

friend clock& operator++(clock &c);

clock& operator++(clock &c)
{
    c.second++;
    c.format();
    return c;
}

后置

改变第一操作数,传递的是引用

返回临时变量,为值

friend clock operator++(clock &c, int i)
{
    clock old(c);
    c.second++;
    c.format();
    return old;
}

friend clock operator++(clock &c, int i)
{
    c.second++;
    c.format();
    return clock(c.hour,c.min,c.second-1);
}

14 模板类

将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象

函数模板

函数模板格式

template  <typename T>
 类型名 函数名(参数表)
 {函数体的定义}

template  <class T>
 类型名 函数名(参数表)
 {函数体的定义}

其中,<模板形参表>可以包含基本数据类型,也可以包含类类型。类型形参需要加前缀typename 或class。如果类型形参多于一个,则每个类型形参都要使用前缀。<模板形参表>中的参数必须是惟一的,而且在<函数定义体>中至少出现一次。
函数模板定义不是一个实实在在的函数,编译系统不为其产生任何执行代码。该定义只是对函数的描述,表示它每次能单独处理在类型形式参数表中说明的数据类型。

函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行。

模板函数有一个特点,虽然模板参数T可以实例化成各种类型,但是采用模板参数T的各参数必须保持完全一致的类型。

克服了C++函数重载“用相同函数名字重写几个函数”的繁琐

template <typename T>
T abs(T x)
{  
    return x < 0 ? -x : x;
}

int main()
{
    int i = -20;
    int j = abs(i);         
    double k = -4.5;
    double t = abs(k);
    return 0;
}
函数模板的特化
template <> 
返回类型 函数名<特化的数据类型>(参数表) {}


template <>  //特化
const char* tmin<const char*>(const char *a,const char *b) 
{
	return (strcmp(a,b)<=0)?a:b;
}
类模板
template <模板参数表>
 class 类名
 { 类成员声明 };
     
// 类模板格式

 template <模板参数表>
 类型名 类名<T>::函数名(参数表)

     
// 类模板外定义成员函数
     
template <class T>    
class Node{ 
private:
   T item; 
public:
   T GetElem();  
};

template <class T>      
T Node<T>::GetElem() {  
   return item;    
}

15 STL

STL的核心内容包括容器、迭代器、算法三部分

STL的容器常被分为顺序容器、关联容器和容器适配器三类

C++提供的顺序类型容器有向量(vector)、链表(list)、双端队列(deque)。
关联容器主要包括集合(set)、多重集合(multiset),map,multiplemap
容器适配器主要指堆栈(stack)和队列(queue)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值