C++
从C到C++
1983年,贝尔实验室(Bell Labs)的Bjarne Stroustrup发明了C++。C++在C语言的基础上扩充和完善,是一种【面向对象】程序设计(OOP)语言。
C++支持面向对象编程(OOP)、【泛型编程】和【过程化编程】。
编程领域广泛:常用于【系统开发】,【引擎开发】等应用领域。
国际化标准组织于2011年9月1日出版发布《ISO/IEC 14882:2011》简称ISO C++标准,取代现行的C++标准、C++98和C++03.
C++新增特性
-
更为严格的类型检查
//eg: int *p; char *q; p = q; //error,不能将 char * 给 int *
-
新增变量
引用
(给变量取别名)//eg: int a = 10; int &b = a; //给a取别名叫b printf("%d %d %p %p\n",a,b,&a,&b);
-
支持面向对象
类
和对象
、继承
、多态
、虚函数
及RTTI
(运行时类型识别) -
新增泛型编程
支持模板
(template
),标准模板库
(STL
) -
支持异常处理
-
支持
函数
及运算符重载
-
支持名字空间
用于管理函数名
、变量
名及类
OOP(Object Oriented Programmings)简介
软件工程
的三个目标:重用性、灵活性、扩展性。
重用性:代码被重复使用,以减少代码量,就是重用性的重要指标。
灵活性:软件系统由很多可以复用的构件随意重构,以达到实现不同的功能,非常灵活。
扩展性:软件系统可以容易地新增需求,基本构建也可以轻松的扩展功能。
面向对象编程(OPP)
OPP是程序设计工程化的一种方法,软件架构的一种思想。
OPP基本原则是程序是由单个能够起到子程序作用的单元或对象组合而成,以达到软件工程的三个主要目标:重用性、灵活性和可扩展性。
-
面向过程设计:
程序 = 数据结构 + 算法
面向过程编程(Procedure Oriented)是以过程为中心,把分析解决问题的步骤流程以函数的形式一步步设计实现。
//面向过程 上学过程(){ 起床 洗漱 吃饭 去学校 }
-
面向对象设计:
对象 = 数据结构 + 算法
程序 = (对象 + 对象 + ...) + 对象间通信机制
面向对象编程(OOP)是以事务为中心。【一切事物皆对象】,通过面向对象的方式,将现实世界的事物抽象成对象。
//面向对象 学生 { 属性: 姓名 年龄 学号 班级 ... 动作: 上学(); 放学(); 上课(); ... }
面向对象设计的概念:类、对象、数据抽象、动态绑定、数据封装、多态性、消息传递;
-
对象(Object)
可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。
-
类(Class)
共享相同属性和方法的【对象集合】。描述一类事物的抽象特点,【类的方法和属性】被称为“
成员
”。 -
封装(encapsulation)
将数据(数据)和方法(操作)捆绑在一起,创造出一个【新的类型】的过程。
将接口与实现分离的过程。
-
继承(inherit)
一个类共享了一个或多个其他类定义的【属性】和【方法】,在这种关系中【子类】可以对【基类】进行扩展、覆盖、重定义。
-
组合
既是类之间的关系也是对象之间的关系。在这种关系中一个对象或者类包含了其他的对象和类。组合描述了“有”关系。
-
多态(polymorphism)
类型理论中的一个概念,一个名称可以表示很多不同类的对象,这些类和一个共同的超类有关。因此,这个名称表示的任何对象可以以不同的方式响应一些共同的操作集合。
-
动态绑定(静态联编)
也称【动态类型】,指的是一个【对象】或者【表达式】的类型直到运行时才确定。通常由编译器插入特殊代码来实现。
-
静态绑定(静态联编)
也称【静态类型】,指的是一个【对象】或者【表达式】的类型在编译时确定。
-
消息传递
指的是一个对象调用了另一个对象的方法(或者称为成员函数)。
-
方法
也称成员函数,是指对象上的操作,作为【类声明】的一部分来定义。方法定义了可以对一个对象执行那些操作。
泛型编程(Generic Programming)
泛型编程(Generic Programming)由Alexander Stepanov、Meng Lee和David Musser在惠普实验室工作时所发明,目的是为了实现C++的STL(Standard Template Library标准模板库)。
C++中的支持机制就是模板(Templates)。模板的实质就是参数化类型,简言之:把特定的类型信息抽象化,抽象成模板参数T。这样就可以编写出任意类型动作一致的类或方法,在使用时才指定实际类型。泛型编程一定程度上杜绝了类型转换。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XBHT1kD0-1629157866077)(https://i.loli.net/2021/08/11/z5Lydvr8NFMeZf1.png)]
编译C++程序
-
预处理:(处理所有 # 开头的东西,例如:头文件展开 宏替换 条件编译等)
g++ -E *.cpp ---> *.i
编译:
g++ -S *.i ---> *.s
汇编:
g++ -c *.s ---> *.o
链接:
g++ *.o ---> a.out
-
C++程序的基本结构
#include <iostream> //引用头文件:预处理指令#include,标准输入输出流
using namespace std; //编译指令:using namespace,名字空间
//入口函数
int main(int argc, char *argv[]) //函数体:{}
{
cout << "faker!" << endl;
return 0; //函数返回
}
C和C++兼容及差异
C++融合了三种编程方式:C语法的【面向过程编程】、C++基于C语法扩展的【面向对象编程】、及C++模板
支持的【泛型编程】。
-
常变量
在变量的基础上加const限定: 存储单元中的值不允许变化。因此常变量又称为【只读变量】(read-only-variable)。const可以修饰类方法,表示方法不能修改类对象;//eg: void prnmsg() const //表示 prnmsg() 是const方法,不能修改类对象 { }
-
强制类型转换
C语言强制类型转换的一般形式:
(类型名)(表达式)
//eg: int i; char ch; ch = (char) i; //将 i 强转为 char
C++新增了以下形式:
类型名(表达式)
//eg: int i; char ch; ch = char (i); //将i强转为 char
-
变量引用
对一个数据可以使用“引用”,这是C++对C的一个重要扩充,引用是一种新的变量类型,它的作用是为一个变量起一个别名.-
注意:不能同时引用两个变量
eg: int a = 10; int c = 20; int &b = a; //给a取别名为 b int &b = c; //error 不能同时引用两个变量
C中函数之间的参数传递方式有:赋值传递方式、地址传递方式;C++增加了函数参数引用
//eg:reference void exchange(int &m, int &n) //给实参a取别名为 m,实参b取别名为 n { m = m ^ n; n = m ^ n; m = m ^ n; return ; } int main() { int a = 10; int b = 20; exchange(a,b); printf("a=%d b=%d\n",a,b); return 0; }
-
-
内联函数语法
inline 存储类型 数据类型 函数名(参数列表);
-
注意:内联函数的限制
使用内联函数可以节省运行时间,但却增加了目标程序的长度。因此一般只将规模很小(一般为5个语句以下)而使用频繁的函数(如定时采集数据的函数)声明为内联函数;
由于多个源文件是分开编译的,要内联就应该把内联函数声明定义在头文件中;
内置函数中不能包括复杂的控制语句,如循环语句和switch语句。
-
函数重载
函数同名,参数【数量、类型、次序】不同的多个函数;
函数行为相似才应该重载,如果函数逻辑一致,参数类型不同则应该是模板;
C语言没有重载语法:类似的解决方法是不定参数,但是针对重载的应用不定参数不合理;
-
函数模板
C++提供了
函数模板
(function template)。建立一个【通用函数】,其【函数类型】和【参数类型】不具体指定,用一个【虚拟的类型】来代表。这个通用函数就称为函数模板
。 -
默认参数
只有函数声明时才能够写默认参数,定义时不能写默认参数;
地址是一个无符号的整形变量(编号)。
再谈结构体
结构体基本语法:
struct 结构体名 {
成员域1;
成员域2;
......
};
//声明一个结构体
struct Demo {
int (*add)(struct Demo &);
int x,y;
};
int add(struct Demo &obj)
{
return obj.x + obj.y;
}
//定义一个结构体
struct Demo obj = {
.add = add,
.x = 1,
.y = 2,
};
int main(int argc, char *argv[])
{
struct Demo *p = &obj;
cout << obj.add(obj) << endl;
cout << p->add(obj) << endl;
return 0;
}
-
C++语法中相对C语法增加了访问权限的概念,有三种:
public
、private
及protected
,默认是public
。public:公共成员,表示可以通过【结构体变量对象】直接访问到成员
private :私有成员,表示仅【结构体成员函数】可以使用的成员
protected:保护成员,表示【被继承的派生对象】可以访问使用的成员
由于增加访问权限,结构体变量就得增加【构造函数】的概念和【this指针】,不然成员函数访问成员数据就是问题。
struct Demo { public: Demo(int x = 1, int y = 1); //构造函数,与结构体同名,用于初始化结构体 int add(void); //成员函数,可以操作私有或保护成员 protected: int x,y; //成员数据,访问权限是保护的 }; Demo::Demo(int x,int y) { this->x = x; //this指针,一个特殊指向对象自己的指针,且仅能在内部使用 this->y = y; } int Demo::add() { return this->x + this->y; } int main(int argc,char *argv[]) { strcut Demo obj(1,2); cout << obj.add() << endl; return 0; }
-
c++中允许在结构体内部定义成员函数(成员方法);
#include <stdio.h>
#include <iostream>
using namespace std;
//定义结构体类型
struct Demo {
public: //公有访问权限,表示在结构体外部可以通过结构体变量访问
int getx() { return x; } //成员方法
void setx(int val) { x = val; } //成员方法
private: //私有访问权限,只能在结构体内部访问
int x; //成员变量
};
int main()
{
struct Demo obj;
//obj.x = 321; //error x是private成员,不能在结构体外部通过变量方法,只能在内部访问。
obj.setx(123); //right setx是public方法,可以通过结构体变量访问
cout << obj.getx() << endl; //right getx()是public方法
return 0;
}
内存模型及名字空间
作用域
作用域(scope)描述了一个【名字】在文件(编译单元)的多大范围内可见。
C++支持三种形式的域:
-
局部域(local scope)
局部域是包含在函数定义或者函数块中的程序文本部分。 -
名字空间域(namespace scope)
【名字空间域】是不包含在【函数声明、函数定义】或者【类定义内】的程序文本部分;
程序的最外层的名字空间域被称作【全局域】(global scope),或【全局名字空间域】(global namespace scope);
对象函数类型以及模板都可以在【全局域】中定义;
可以利用名字空间定义(namespace definition )来定义用户声明的(user-declared) 的名字空间;
每个用户声明的名字空间都是一个不同的域,它们都与全局域不同,与全局域相同的是用户声明的名字空间可以包含对象函数类型和模板的声明与定义,以及被嵌套其内的用户声明的名字空间。
//eg: namespace A { int i; //i的作用域为名字空间域 void prnmsg() //prnmsg的作用域为名字空间域 { printf("prnmsg...\n"); } }
-
类域(class scope)
每个类定义都引入了一个独立的类域eg: class Demo { public: void prnmsg() //prnmsg的作用域是类域 { } private: int myval; //myval的作用域是类域 };
-
变量的作用域
局部变量
的作用域为局部,仅能【代码块内】可见;“
{}
”就是【代码块】的作用域,函数、switch、for、while
等等;全局变量的作用域为全局的,在定义出开始直到文件尾可见;
自动变量的作用域是局部的,静态变量的作用域要看其定义的位置;
函数原型作用域:
在函数原型【声明】时候,在
“()“
内声明的参数列表中的变量,仅能在”()“
内可见。正因为如此,所以函数声明时,有无形参,形参是什么都不重要。名字空间作用域:
在指定名字空间中定义的
变量
,在【整个名字空间】中可见。类作用域:
类中声明的
变量
,在【整个类内部】可见,不管是什么权限的变量。 -
函数的作用域
可以是整个类,也可以是整个名字空间,但不能是局部的。
链接性及存储特性
链接性
(linkage)描述了名称如何在各个单元中的共享。
外部链接:是指名称可以在【文件间】的函数共享;
内部链接:名称仅仅能在一个【文件中】的函数共享。
-
变量的链接:
存储类 时期 作用域 链接性 声明方式 局部变量(auto) 自动 模块内(代码块) 空 代码块内 register变量 自动 模块内 空 代码块内,使用关键字register 全局变量 静态 文件 外部链接 所有函数之外 static全局变量 静态 本文件 内部链接:名称仅仅能在一个文件中的函数共享 所有函数之外,使用关键字static static局部变量 静态 模块内 空 代码块内,使用关键字static -
函数的链接性及存储性
函数的【存储持续性】为【静态】的;
【默认】的情况下,函数的【链接性】为【外部】的;
要引用函数,可以加
extern
【说明限定符】;加
static
说明限定符的函数,链接性为内部的;如果【和外部的函数重名】,则【静态的函数替换】之。非内联函数受单定义规则限制,但是内联函数则不然。
C++
运行内联函数的定义放在头文件内。不过一个内联函数所有的定义必须相同。 -
语言链接性
如果C语言编写的函数需要被C++代码使用到,就需要注意到语言的链接性。
extern "C" void prnmsg(const char *ptr); //声明使用c中的prnmsg函数 //由编译器决定,若编译器是 g++,则prnmsg为c++ 函数,若编译器是gcc,则prnmsg是c函数 extern void prnmsg(const char *ptr); extern "C++" void prnmsg(const char *ptr); //声明使用c++中的prnmsg函数
//language_link.cpp #include <stdio.h> extern "C" void prnmsg(const char *str); int main() { prnmsg("hello C++"); //调用c的prnmsg() return 0; }
#include <stdio.h> void prnmsg(const char *str) { if(NULL != str) printf("%s\n",str); return; }
动态内存
C中malloc/free
分配管理的堆区就是动态内存,C++中则引入运算符new/delete
来分配内存。
运算符new和new[]调用的函数如下:
void * operator new[std::size_t]; //used by new
【运算符】delete
和delete[]
调用的函数如下:
void operator delete(void *);
void operator delete[] (void *);
定位new
运算符
int * p2 = new(buffer) int;
invokes new(sizeof(int),buffer)
名字空间(解决重名)
语法形式
namespace 名称 {
//变量
//函数
//类
...
}
//namespace
#include <iostream>
#include <stdio.h>
namespace A {
int i = 123;
void prnmsg()
{
printf("i=%d\n",i);
}
}
namespace B{
int i = 321;
void prnmsg()
{
printf("i=%d\n",i);
}
}
int i = 10;
//using namespace A; //声明使用整个名字空间A 注意歧义
//using namespace B;
int main()
{
int i = 20;
#if 0
//不声明直接使用
A::prnmsg(); //不声明直接使用,使用时:名称::变量名
B::prnmsg();
#else
//声明再使用
using A::prnmsg; //声明使用A中的prnmsg函数
prnmsg();
#endif
printf("i=%d\n",::i); //表示访问全局i
return 0;
}
标准输入输出流
输入和输出不是由C++
本身定义的,而是在【编译系统】提供的I/O库
中定义的;
C++的输出和输入是用“流”(stream)
的方式实现的;
在定义流对象时,系统会在内存中开辟一段缓冲区,用来暂存输入输出流中的数据。在执行输入输出语句时,先把数据顺序存放在输出缓冲区中,直到输出缓冲区满或遇到输入输出语句中的endl(或'\n', ends,flush)
为止,此时将缓冲区中已有的数据一起输出,并清空缓冲区。
标准类库中提供的用于输入和输出的流对象
输出流(cout)
输出流即从程序中输出一系列的字符串,使用cout对象
。
//引用头文件
#include <iostream>
using namespace std;
//基本形式如下:
cout<<表达式1<<表达式2<<……; //<<插入,字符串插入到输出流,然后输出到显数器
换行
控制符endl;
换行符:\n;
输入流(cin)
输入流即向程序输入一系列的字符串,使用cin对象
。
//引用头文件
#include <iostream>
using namespace std;
//基本形式如下:
cin >> 变量1 >> 变量2 >> …; //>>提取,键盘输入字符串到输入流,提取到计算机指定的变量
//注意:输入时,以空行或回车作为一个变量输入结束。
#include <iostream>
using namespace std;
int main()
{
int i;
char ch;
float f;
cin >> i >> ch >> f; //cin输入时,系统会自动识别输入数据的类型
cout << "i=" << i << endl; //cout输出时,系统会自动判别输出数据的类型
cout << "ch=" << ch << endl;
cout << "f=" << f << endl;
return 0;
}
格式控制符号
设置状态标志流成员函数setf
一般格式:long ios::setf(long flags)
调用格式:流对象 . setf(ios::状态标志)
类和对象
OOP思想
按照生活中处理问题的思路,一个复杂的事物总是由许多部分组成,一切事物皆对象(object)。对象可大可小,是构成系统的基本单位。
对象(object)= 属性(attribute) + 行为(behavior)
抽象
抽象是对具体对象(问题)进行概括,描述这一类对象的公共性质;
数据抽象:描述某类对象的【属性】或【状态】(对象相互区别的物理量);
代码抽象:描述某对象的共有的行为特征或具有的功能;
抽象的实现:通过【类的定义】。
OOP的三个基本特征是:封装、继承、多态
封装:使得代码【模块化】;
继承:扩展已存在的代码模块(类),为了代码重用
多态:实现【接口重用】
封装
将抽象出的【数据成员】、【代码成员】相结合,将它们视为一个整体。目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
实现封装:其表现形式【类定义】中的:{ }
分类
层层分类,使概念逐渐细化、具体化。例如,生物分为动物和植物,动物又分为脊椎动物和无脊椎动物,脊椎动物包括哺乳动物,哺乳动物又分为猫、狗…,等等
继承
子类继承父类的【属性】、【方法】,并可定义自己的特有属性与方法。提高软件开发效率,为“软件重用”
(software reusability)提供强有力的支持。
多态
C++
中的多态性(polymorphism):由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。
类的声明定义
C++
中【对象的类型】称为类(class)
,类代表了某一批对象的【共性】和【特征】,类是对象的抽象,而对象是类的具体实例(instance)。
先声明一个类类型,然后用它去定义若干个同类型的对象。对象就是类类型的一个【变量】。可以说类是对象的模板,是用来定义对象的一种抽象类型。
类是抽象的,不占用内存,而对象是具体的,占用内存空间。
类是用户自己指定的类型。如果程序中要用到类类型,必须自己根据需要进行声明,或者使用别人设计好的类。C++标准本身不提供现成的类的名称、结果和内容。
类定义的语法形式
class 类名
{
private:
私有的数据和成员函数;
public:
公用的数据和成员函数;
protected:
保护的数据和成员函数;
};
//eg1:类内部定义成员方法
class Demo {
public:
void setval(int val) { myval = val; }
int getval() { return myval; }
private:
int myval; //私有成员变量
};
//eg2:类外部定义成员方法
class Demo{
public:
void setval(int val);
int getval();
private:
int myval; //私有成员变量
};
void Demo::setval(int val) { myval = val; }
int Demo::getval() { return myval; }
-
注意:类定义包含两部分:
类头class head由关键字class及其后面的类名构成;
类体class body由一对花括号包围起来,类定义后面必须接一个分号或一列声明。
类对象的定义及使用
-
对象的定义:
类名 对象名;
eg:Demo obj;
-
对象指针的定义:
类名 * 对象指针名;
eg:Demo *p;
-
通过对象成员运算符访问公共成员:
对象名.成员名
eg:obj.setval();
-
对象指针通过“->”访问公共成员:
对象指针->成员名
eg:p->setval();
-
注意:
private:私有访问限定符,仅能被类【内部】访问;
protected:保护访问限定符,能被【类内部】访问或【子类继承】访问。
-
#ifndef CLS_H
#define CLS_H
class Demo{
public:
void setval(int);
int getval(void);
private:
int myval;
};
#endif
#include <iostream>
#include "cls.h"
void Demo::setval(int val)
{
myval = val; //将val的值赋给私有成员变量myval
}
int Demo::getval(void) { return myval; }
#include <iostream>
#include "cls.h"
using namespace std;
int main()
{
Demo obj; //局部对象
Demo *p = &obj; //将对象obj的地址给对象指针p
cout << sizeof(obj) << endl;
//obj.myval = 321; //error 不能通过类对象访问对象的私有成员
#if 0
obj.setval(123); //right 因为setval()是公有成员方法,可以通过类对象在类外部访问
cout << obj.getval() << endl; //right
#else
p->setval(321); //通过对象指针p访问对象成员方法
cout << p->getval() << endl;
#endif
return 0;
}
如果设计一个类时,没有显示声明定义构造函数、析构函数、拷贝构造函数、赋值运算符、地址运算符,则编译器会自动生成。
构造函数和析构函数
C++
提供了构造函数(construct)
来处理【对象的初始化】。
-
构造函数是一种特殊的成员函数,与其他成员函数不同:
A、不需要用户来调用它也【不能调用】,而是在【建立对象】时自动执行;
B、
构造函数
的名字必须与类名同名,而不能由用户任意命名,以便编译系统能识别它并把它作为构造函数处理;
C、没有返回值;
D、构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数;
E、如果用户不设计,则编译器自动生成一个。 -
构造函数声明一般格式为:
构造函数名(类型 1 形参1,类型2 形参2,…);
-
定义对象的一般格式为 :
类名 对象名(实参1,实参2,…);
#include <iostream> using namespace std; class Demo { public: Demo(int val) //构造函数:无返回值、函数名与类名同名、可以有形参、定义对象时自动调用 { myval = val; //给私有成员变量赋值 TRACE() } ~Demo() { TRACE() } //析构函数:无返回值、无参数、可显式调用 public: int getval() { return myval; } private: int myval; }; int main() { Demo obj(123); //构造对象时给构造函数传参 cout << obj.getval() << endl; return 0; }
构造函数
可以重载
。
默认参数构造函数
指不需要用户指定实参就能够被调用的构造函数,C++有默认参数的概念。
-
C++允许赋予函数参数默认值,即在调用该函数时,可以不写某些参数的值,编译器会自动把默认值传递给调用语句中。
//eg: int add(int x, int y=0) //给形参y指定默认值 0 { return (x+y); } int main() { printf("%d\n",add(10)); //函数调用时,将实参10传给形参x,y使用默认值0 return 0; }
-
默认值可以在声明或定义中设置;也可在声明或定义时都设置,都设置时要求默认值是相同的。
-
注意事项:
如果不是在声明中设置默认值,而在定义中设置,那么定义一定要放在函数调用之前。
不能将实际值传递给引用类型的参数。
可以将变量作引用类型参数的默认值,这时变量必须是已经声明且是全局变量。
若给某一参数设置了默认值,那么在参数表中其后所有的参数都必须也设置默认值。
在调用时,若给已经设置默认值的参数传递实际值,既要取代默认值,则在参数表中被取代参数的左边定义所有的参数,无论是否有默认值,都必须传递实际参数。
- 注意:
函数重载与默认参数一起使用时,有可能产生歧义;
- 注意:
eg:
//函数重载:函数名相同,参数不同(个数不同 类型不同 个数和类型都不同)
//在同一个作用域
int add(int x,int y=0) //给形参y指定默认参数值为 0
{
return (x+y);
}
int add(int x, int y,int z=0)
{
return (x+y+z);
}
int main()
{
printf("%d\n",add(20)); //根据实参自动适配函数
//printf("%d\n",add(10,20)); //有歧义 两个版本的add都适合
printf("%d\n",add(10,20,30)); //根据实参自动适配函数
return 0;
}
限制构造函数
拷贝构造
有时候需要用一个类对象初始化该类的另一个对象。设计一个拷贝构造函数(copy constructor)
来实现一个类对象向该类的另一个对象作拷贝是通过【依次拷贝每个非静态数据成员】。
-
注意:
浅拷贝:只是将【一个对象成员变量】的值拷贝给另外一个对象;浅拷贝要注意【堆区空间多次回收造成段错误】;
深拷贝:若对象中的【指针成员变量指向堆区空间】,在拷贝时需要【给新对象开辟新的堆区空间】。#include <iostream> #include <assert.h> using namespace std; class Array{ public: Array(int len) { num = len; ptr = new char [num]; assert(NULL != ptr); for(int i=0;i<num;i++) ptr[i] = i; TRACE() } #if 0 Array(Array &obj) //浅拷贝 { num = obj.num; ptr = obj.ptr; for(int i = 0;i<num;i++) ptr[i] = obj.ptr[i]; TRACE() } #else Array(Array &obj) //深拷贝 { num = obj.num; ptr = new char [num]; //给新对象开辟新的堆区空间 assert(NULL != ptr); for(int i = 0;i<num;i++) ptr[i] = obj.ptr[i]; TRACE() } #endif ~Array() { TRACE() if(NULL != ptr) delete [] ptr; } private: char *ptr; //保存数组首地址 int num; //保存数组个数 }; int main() { Array obj1(10); Array obj2(obj1); return 0; }
析构函数(destructor)
析构函数(destructor)
是在一个【对象的生命期】即将结束的时候,应该回收该对象占有的资源,或是完成一些清理工作;
析构函数
既没有返回值,也没有函数参数,因此它不能被重载。
析构函数
的语法一般是在类名
前加一个“~“
;当【对象消亡】的时候
析构函数
会【自动被调用】。可以显示调用析构函数。
一般的,不需要进行析构函数显示调用,也会有特殊需求:譬如对象是静态的时候,存在堆区分配,当程序未结束时候,需要释放堆,这个时候就可以显式调用析构函数来完成。
如果显示调用了析构函数,此时析构函数和普通成员函数是一样的,并不会造成对象被销毁。
#include <iostream>
using namespace std;
class Demo{
public:
Demo(int val) //构造函数:无返回值、函数名与类名同名、可以有形参 、定义对象时自动调用
{
myval = val; //给私有成员变量赋值
TRACE()
}
~Demo() //析构函数:无返回值 无参数 可显式调用
{
TRACE()
}
public:
int getval()
{
return myval;
}
private:
int myval;
};
Demo obj(321); //定义全局的对象 obj
int main()
{
Demo *p = new Demo(123); //在堆区给对象开辟空间
cout << p->getval() << endl; //对象指针访问成员
delete p; //回收堆区空间
return 0;
}
this指针
this指针
是一个特殊的指针,【指向类对象自身的首地址】;
每个类对象的成员函数都一个this指针,【指向调用对象】,如果要【引用整个对象】则
*this
;
this
指针仅能在【类内部使用】。
#include <iostream>
using namespace std;
class Demo{
public:
Demo(int val)
{
this->myval = val; //this指针是特殊的指针,保存对象的地址,只能在类内部访问
cout << __func__ << ":" << __LINE__ <<":" << this << endl;
}
~Demo()
{
cout << __func__ << ":" << __LINE__ <<":" << this << endl;
}
private:
int myval;
};
Demo obj1(111);
int main()
{
//构造顺序:obj1 -> obj2 -> obj3 -> obj4
//析构顺序:obj4 -> obj2 -> obj3 -> obj1
//注意:生命周期相同的对象遵循:先构造的后析构,后构造的先析构
cout << &obj1 << endl;
Demo obj2(222);
cout << &obj2 << endl;
static Demo obj3(333);
cout << &obj3 << endl;
Demo *p = new Demo(444);
cout << p << endl;
delete p;
return 0;
}
static静态成员
可以将【类的成员】声明成静态的(关键字static
修饰的成员函数或成员变量)。
静态成员没有对象的
this指针
;静态成员不和具体的对象关联,也不能直接访问类的其他成员。
语法形式
class 类名 {
public:
....
public:
static 数据类型 方法名(参数列表) //静态成员方法
{
语句块;
}
private:
static 数据类型 变量名; //静态成员变量
};
//eg:
class Demo {
public:
Demo() { TRACE() }
public:
static int getval() //静态成员方法
{
return myval;
}
private:
static int myval; //静态成员变量
};
静态成员变量定义
在类外部:
数据类型 类名::静态变量名 = 初值;
//eg:
int Demo::myval = 123; //声明定义初始化
静态成员
使用方法
类名::成员名
//eg:
cout << Demo::getval() << endl;
-
注意
静态成员
与对象
无关,即对象存不存在静态成员都存在;
不能使用初始化列表初始化静态成员;
#include <iostream>
using namespace std;
class Demo {
public:
Demo() { TRACE() }
~Demo() { TRACE() }
public:
static int getval() //静态方法
{
return myval;
}
int *getvalAddr()
{
return &(this->val);
}
int *getmyvalAddr()
{
return &myval;
}
private:
static int myval; //静态成员myval 与对象无关,是类的成员
int val; //普通成员
};
int Demo::myval = 123; //静态成员的声明定义初始化
int main()
{
Demo obj1;
Demo obj2;
cout << sizeof obj1 << endl;
cout << sizeof obj2 << endl;
#if 0
cout << obj1.getvalAddr() << endl;
cout << obj2.getvalAddr() << endl;
cout << obj1.getmyvalAddr() << endl;
cout << obj2.getmyvalAddr() << endl;
#endif
cout << Demo::getval() << endl; //静态成员访问 只需 类名::成员 即可
cout << obj.getval() << endl;
return 0;
}
const成员
如果需要保证成员函数不会修改对象,譬如成员函数内部要使用this指针,则需要const成员函数
。
在类内声明语法形式:
<数据类型> <函数名> (<参数列表>) const;
在类外定义语法形式:
<数据类型> <类名> :: <函数名> (<参数列表>) const
{
.......
}
只要类方法不修改对象就应该将其声明为const
。
const对象
-
定义常对象的一般形式为
类名 const 对象名[(实参表列)];
-
也可以把
const
写在最左面:
const 类名 对象名[(实参表列)];
常对象
中的【所有成员的值】都不能被修改,故希望保证【数据成员】不被改变的对象
,就应该声明为常对象
。
如果一个对象被声明为常对象
,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数
和析构函数
)。//eg: class Demo { public: Demo(int a=0, int b=0):x(a),y(b) { TRACE() } //初始化列表初始化成员变量 ~Demo() { TRACE() } public: void sety(int val) { this->y = val; } int getx() const { return this->x; } int gety() const { return this->y; } private: const int x; int y; }; int main() { const Demo obj(123,321); //obj是const对象 //obj.sety(111); //sety是非const成员方法,不能被const对象调用 cout << obj.getx() << endl; cout << obj.gety() << endl; return 0; }
#include <iostream> using namespace std; class Demo{ public: Demo(int val = 123):myval(val) { TRACE() } ~Demo() { TRACE() } public: void setval(const int val) { this->myval = val; } int getval() const //getval()是const方法,不能修改类对象 { return this->myval; } private: static int val; int myval; }; int Demo::val = 222; int main() { const Demo obj(321); //obj是只读对象,表示不能通过成员方法修改对象的成员 等价:Demo const obj(321); //obj.setval(333); //ERROR obj是const对象,不能通过对象访问对象的非const方法 cout << obj.getval() << endl; //right 可以通过const对象访问对象的const方法 return 0; }
const成员变量
如果一个类对象
的【数据成员】希望被保护,也可以使用const
关键字来声明,其用法与一般【只读变量】相似。
const
【数据成员】的值不能改变的,因此const
修饰的【成员变量】只能使用【初始化列表】初始化。
class Demo {
public:
Demo(int val1,int val2=0):myval(val1),val(val2) //初始化列表初始化const成员
{
//myval++; //error myval是const变量,只能初始化
val++;
TRACE()
}
~Demo() { TRACE() }
public:
int getval() const //const成员方法:不能修改类对象
{
return myval; //right
}
private:
const int myval; //const成员变量
int val;
};
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 123):myval(val),x(111) //使用初始化列表给对象的const成员myval初始化为val
{
//this->myval = val; // ERROR 赋值 mtval是const成员,只能初始化,初始化之后不能被修改
this->x = val; // right x是非const成员,在任意时候都可以修改
cout << "x=" << x << endl;
TRACE()
}
~Demo() { TRACE() }
public:
int getval() const //getval()是const方法,不能修改类对象
{
return this->myval;
}
private:
static int val;
const int myval; //myval是只读变量,只能初始化
int x;
};
int Demo::val = 222;
int main()
{
Demo obj(321); //构造对象
cout << obj.getval() << endl;
return 0;
}
const成员方法
const
修饰的【成员方法】不能修改【类对象】;
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 123)
{
this->myval = val;
TRACE()
}
~Demo() { TRACE() }
public:
int getval() const //getval()是const方法,不能修改类对象
{
//(this->myval)++; //error const成员方法不允许修改类对象
val++; //right val是static成员,与对象无关
return this->myval;
}
static int val;
private:
int myval; //成员变量
};
int Demo::val = 222;
int main()
{
Demo obj(321); //构造对象
cout << obj.getval() << endl;
cout << Demo::val << endl;
return 0;
}
new_delete
#include <stdio.h>
#include <string.h>
int main()
{
int *p = new int; //在堆区使用new运算符开辟一个int大小的空间,并返回开辟空间的首地址。
*p = 123;
printf("*p=%d\n",*p);
delete p; //释放p指针指向的堆区空间
char *q = new char [1024]; //在堆区开辟 1024个char大小的空间
strcpy(q, "hello world");
printf("%s\n",q);
delete [] q;
return 0;
}
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val=0) //构造函数指定默认参数值
{
myval = val; //给私有成员变量赋值
TRACE()
}
~Demo() { TRACE() } //析构函数:无返回值 无参数 可显式调用
public:
int getval()
{
return myval;
}
private:
int myval;
};
int main()
{
Demo obj; //构造对象时可以不给构造函数传参,因为构造函数指定了默认参数值
cout << obj.getval() << endl;
return 0;
}
友元
友元函数
友元函数
的【声明】可以放在【类的私有部分】,也可以放在【公有部分】,它们是没有区别的,都说明是该类的一个友元函数
,即某个函数是类的友元。
【一个函数】可以是多个类的友元函数
,只需要在各个类中【分别声明】。
友元函数
的调用与一般【函数的调用方式】和原理一致。
//eg:
Demo obj;
cout << getval(obj) << endl;
语法形式:
class 类名 {
public:
成员方法
friend 数据类型 函数名(参数列表); //声明函数是类的友元
private:
成员变量;
};
//eg:
class Demo {
public:
Demo(int val=0):myval(val) { TRACE() }
~Demo() { TRACE() }
public:
void setval(int val)
{
this->myval = val;
}
friend int getval(Demo &); //指定getval是类Demo的友元函数,可以访问类中所有权限的成员 (private/public/protected)
private:
int myval;
};
int getval(Demo &obj) //必须指定你是谁的友元
{
obj.myval = 123; //允许友元函数访问对象的私有成员变量 myval
obj.setval(321); //友元函数访问对象的公有成员方法 setval()
return obj.myval;
}
友元类
一个类是另外一个类的友元;
语法形式:
class 类名1 {
public:
成员方法
friend class 类名2;//声明 类2 是 类1的友元;
private:
成员变量;
};
//eg:
class A {
public:
A(int val=0):myval(val) { TRACE() }
~A() { TRACE() }
public:
int getval() const
{
return myval;
}
friend class B; //声明类B是类A的友元
private:
int myval;
};
class B {
public:
B(int val):myval(val) { TRACE() }
~B() { TRACE() }
public:
void setval(A &obj) //指定setval是A的友元
{
obj.myval = this->myval; //访问类A的私有成员变量myval
}
private:
int myval;
};
#include <iostream>
using namespace std;
class A {
public:
A(int val = 0) : myval(val) { TRACE() }
~A() { TRACE() }
public:
int getval() const
{
return this->myval;
}
friend class B; //声明类B是类A的友元类,说明类B中的成员方法可以直接操作类A的私有数据成员
private:
int myval;
};
class B {
public:
B(int val = 0) : val(val) { TRACE() }
~B() { TRACE() }
public:
void setval(A &obj) //设置类A中的私有成员变量myval的值
{
obj.myval = this->val;
}
private:
int val;
};
int main()
{
A obja;
B objb(123);
objb.setval(obja);
cout << obja.getval() << endl;
return 0;
}
-
3.说明
友元关系不能被继承;
友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在【类中是否有相应的声明】;
友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明。
友元成员函数
某个类的成员方法是另外一个类的友元
语法形式:
class 类名1 {
public:
成员方法
friend 返回值类型 类名2::成员方法名(参数列表); //声明类2的成员方法是类1的友元
private:
成员变量;
};
//eg:
class A; //前向声明
class B {
public:
B(int val):myval(val) { TRACE() }
~B() { TRACE() }
public:
void setval(A &); //指定setval是A的友元
private:
int myval;
};
class A {
public:
A(int val=0):myval(val) { TRACE() }
~A() { TRACE() }
public:
int getval() const
{
return myval;
}
friend void B::setval(A &); //声明B类的成员方法setval是类A的友元
private:
int myval;
};
void B::setval(A &obj) //类外部定义成员方法setval
{
obj.myval = this->myval; //访问类A的私有成员变量myval
}
#include <iostream>
using namespace std;
class A; //前向声明
class B {
public:
B(int val = 0) : val(val) { TRACE() }
~B() { TRACE() }
public:
void setval(A &); //设置类A中的私有成员变量myval的值
private:
int val;
};
class A {
public:
A(int val = 0) : myval(val) { TRACE() }
~A() { TRACE() }
public:
int getval() const
{
return this->myval;
}
//声明类B中的成员方法setval()是类A的友元成员函数,说明类B中的成员方法setval()可以直接操作类A的私有数据成员
friend void B::setval(A &);
private:
int myval;
};
void B::setval(A &obj) //设置类A中的私有成员变量myval的值
{
obj.myval = this->val;
}
int main()
{
A obja;
B objb(123);
objb.setval(obja); //通过B对象的成员方法setval()操作A对象的自由成员变量myva
cout << obja.getval() << endl;
return 0;
}
运算符重载
函数可以重载,运算符也可以重载。
C++
中预定义的运算符的操作对象只能是【基本数据类型】,但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这是就必须在C++
中重新定义这些运算符,赋予运算符新的功能,使它能够用于特定类型执行特定的操作。
譬如实现一个字符串类,利用运算符的形式操作显然比较直观、方便、易于理解。
#include <iostream>
using namespace std;
int main()
{
string a = "hello";
string b = "world";
cout << a + b << endl; //两个字符串连接
cout << (a == b) << endl; //比较两个字符串是否相等 相等:1 不想等:0
a = b; //字符串赋值,将b对象的字符串拷贝给a对象的字符数组空间
cout << a << endl;
return 0;
}
语法形式:
返回值类型说明符 operator 运算符(参数列表)
{
语句块;
}
- 注意 可以被重载的运算符:
算术运算符:
+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
- 不被重载的运算符:
逗号“.”
、?号表达式“? :”
、siezof
、作用域“::”
、指针运算符“*”
友元运算符重载
运算符重载作为类的友元;
语法形式:
class 类名{
public:
成员方法;
friend 返回值类型 operator 运算符(参数列表);//声明运算符重载是类的友元
private:
成员变量;
};
类外定义格式:
返回值类型 operator 运算符(参数列表)//运算符重载的实现
{
语句块;
}
eg:
class Demo {
public:
Demo(int val):myval(val) { TRACE() }
public:
int getval() const
{
return this->myval;
}
friend Demo operator + (Demo &, Demo &);//声明运算符重载是类Demo的友元
private:
int myval;
};
//重载运算符 +
//+是双目运算符,需要指定两个参数
Demo operator + (Demo &obj1, Demo &obj2)
{
Demo obj3(obj1.myval+obj2.myval);
return obj3;
}
int main()
{
Demo a(222);
Demo b(444);
Demo c = operator +(a,b); //等价: Demo c = a + b;
cout << c.getval() << endl;
cout << a.getval() << endl;
cout << b.getval() << endl;
return 0;
}
一般的,友元函数重载
双目运算符(有两个操作数,通常在运算符的左右两侧),参数表中的个数为两个。若是重载单目运算符(只有一个操作数),则参数列表中只有一参数。
成员函数运算符重载
指运算符重载
是【类的成员】。
语法形式
class 类名 {
public:
成员方法;
返回值类型 operator 运算符(参数列表); //声明运算符重载是类的成员
private:
成员变量;
};
返回值类型 类名::operator 运算符(参数列表) //运算符重载的实现
{
语句块;
}
eg:
class Demo {
public:
Demo(int val):myval(val)
{
cout << __func__ << ":" << __LINE__ << this << endl;
}
public:
int getval() const;
Demo & operator += (Demo &); //声明运算符重载 +=
private:
int myval;
};
int Demo::getval() const
{
return this->myval;
}
/************************************************************************************
重载运算符 +=
+=:双目运算符,由于运算符重载是类的成员,则只需传一个参数即可,另外一个参数可以使用this指针直接访问
a += b;返回a
************************************************************************************/
Demo & Demo::operator += (Demo &obj)
{
this->myval += obj.myval;
return (*this); //返回this指针指向的对象
}
int main()
{
Demo a(222);
Demo b(444);
a += b; //等价:a.operator += (b);
cout << a.getval() << endl;
cout << b.getval() << endl;
return 0;
}
- 注意:
一般的,对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数;
同样的是重载,为什么和友元函数在参数的个数上会有所区别的?
原因在于友元函数,没有this指针。
注意事项
A、除关系运算符"
.
"、成员指针运算符"->
*"、作用域运算符"::
"、sizeof
运算符和三目运算符"?:
"以外,C++
中的所有运算符都可以重载(其中“=
”和“&
”不必用户重载);
B、重载运算符限制在C++
语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符;
C、运算符重载的实质是【函数重载】,遵循函数重载的选择原则;
D、重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构;
E、运算符重载不能改变该运算符用于内部类型对象的含义;
F、运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符;
G、重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数;
H、重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则;
I、运算符重载可以通过【成员函数】的形式,也可是通过【友元函数】,非成员非友元的普通函数。
重载双目运算符 +
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 0) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
int getval() const
{
return this->myval;
}
friend Demo operator + (Demo &, Demo &); //运算符重载是类的友元,即友元运算符重载
private:
int myval;
};
//重载双目运算符 +
//友元运算符重载时,双目运算符需要两个操作数,所以参数是两个
Demo operator + (Demo &obj1, Demo &obj2)
{
#if 0
Demo obj;
obj.myval = obj1.myval + obj2.myval;
return obj;
#else
TRACE()
Demo obj(obj1.myval + obj2.myval);
return obj;
#endif
}
int main()
{
Demo a(123);
Demo b(321);
Demo c = a + b; //等价:Demo c = operator +(a,b);
cout << c.getval() << endl;
return 0 ;
}
重载单目运算符 前++
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 0) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
int getval() const
{
return this->myval;
}
friend Demo &operator ++ (Demo &);
friend Demo operator ++ (Demo &,int);
private:
int myval;
};
/**********************************************
重载单目运算符 前++
友元运算符重载时,单目运算符需要一个操作数,所以参数是一个
++a 的结果是 a
**********************************************/
Demo &operator ++ (Demo &obj)
{
++(obj.myval);
return obj;
}
/************************************************
重载单目运算符 后++
友元运算符重载时,单目运算符需要一个操作数,所以参数是一个
Demo b = a++; a++的结果是b
************************************************/
Demo operator ++ (Demo &obj,int val) //int val区分前++和后++
{
Demo tmp(obj.myval);
obj.myval++;
return tmp;
}
int main()
{
Demo a(123);
Demo b(321);
++a;
cout << a.getval() << endl; //124
Demo c = (b++);
cout << c.getval() << endl; //321
cout << b.getval() << endl; //322
return 0 ;
}
重载运算符 !
#include <iostream>
using namespace std;
class Demo {
public:
Demo(bool flag = true)
{
this->flag = flag;
TRACE()
}
friend bool operator ! (Demo &);
private:
bool flag;
};
//重载运算符 !
//!是单目运算符,需要指定一个参数
bool operator ! (Demo &obj)
{
return !obj.flag;
}
int main()
{
Demo a(true);
Demo b(false);
if(!a)
cout << "a is false" << endl;
else
cout << "a is true" << endl;
if(!b)
cout << "b is false" << endl;
else
cout << "b is true" << endl;
return 0;
}
#include <iostream>
using namespace std;
class Complex {
public:
Complex(double real = 0, double imag = 0) : _real(real),_imag(imag) { TRACE() }
~Complex() { TRACE() }
public:
Complex operator + (Complex &obj) //Complex obj = obj1 + obj2;
{
TRACE()
#if 0
Complex tmp(this->_real + obj._real, this->_imag + obj._imag);
return tmp;
#else
return Complex(this->_real + obj._real, this->_imag + obj._imag);
#endif
}
void display()
{
cout << this->_real << "+" << this->_imag << "*i" <<endl;
}
private:
double _real;//实部
double _imag;//虚部
};
int main()
{
Complex obj1(3,4);
Complex obj2(3,4);
Complex obj = obj1 + obj2;
obj.display();
return 0;
}
#include <iostream>
using namespace std;
class String {
public:
String(char *str = NULL) : ptr(str)
{
int len = strlen(str) + 1;
this->ptr = new char [len];
assert(NULL != ptr);
strcpy(ptr, str);
}
~String()
{
delete [] ptr;
}
private:
char *ptr;
};
int main()
{
String a("hello");
return 0;
}
重载单目运算符!
#include <iostream>
using namespace std;
class Demo {
public:
Demo(bool flag = true) : flag(flag) { TRACE() }
~Demo() { TRACE() }
public:
bool operator !() //成员函数运算符重载,重载单目运算符!
{
return (!this->flag);
}
private:
bool flag;
};
int main()
{
Demo a(true);
Demo b(false);
if(!a)
cout << "a is false" << endl;
else
cout << "a is true" << endl;
if(!b)
cout << "b is false" << endl;
else
cout << "b is true" << endl;
return 0;
}
成员函数运算符重载
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 0) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
int getval() const
{
return this->myval;
}
Demo operator + (Demo &); //成员函数运算符重载
private:
int myval;
};
//成员函数运算符重载时,若运算符是双目运算符,只需指定一个参数即可,因为运算符重载是类的成员函数,有this指针
Demo Demo::operator + (Demo &obj)
{
#if 1
return Demo(this->myval + obj.myval); //匿名对象
#else
Demo tmp(this->myval + obj.myval); // 等价 Demo tmp; tmp.myval = this->myval + obj.myval;
#endif
}
int main()
{
Demo a(123);
Demo b(321);
Demo c = a + b; //等价:Demo c = a.operator + (b);
cout << c.getval() << endl;
return 0;
}
重载 +=
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val):myval(val) { TRACE() }
public:
int getval() const;
Demo & operator += (Demo &);//声明运算符重载 +=
private:
int myval;
};
int Demo::getval() const
{
return this->myval;
}
/************************************************************************************
重载运算符 +=
+=:双目运算符,由于运算符重载是类的成员,则只需传一个参数即可,另外一个参数可以使用this指针直接访问
a += b;返回a
************************************************************************************/
Demo & Demo::operator += (Demo &obj)
{
this->myval += obj.myval;
return (*this); //返回this指针指向的对象
}
int main()
{
Demo a(222);
Demo b(444);
a += b; //等价:a.operator += (b);
cout << a.getval() << endl;
cout << b.getval() << endl;
return 0;
}
重载<<
运算符
#include <iostream>
using namespace std;
class Demo {
public:
Demo(int val = 0) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
int getval() const
{
return this->myval;
}
Demo operator << (int bits) //重载<<运算符
{
Demo tmp;
tmp.myval = this->myval << bits;
return tmp;
}
friend ostream &operator << (ostream &, Demo &);
private:
int myval;
};
ostream &operator << (ostream &os, Demo &obj)
{
TRACE()
os << obj.myval;
return os;
}
int main()
{
Demo a(1);
Demo b = (a << 2); //Demo b = a.operator(2)
//cout << b.getval() << endl;
cout << b << endl; //operator << (cout,b);
return 0;
}
#include <iostream>
using namespace std;
int & add(int &t)
{
++t;
cout << &t << endl;
return t;
}
int main()
{
int a = 10;
int &b = add(a);
cout << b << endl;
cout << &a << endl;
cout << &b << endl;
return 0;
}
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;
class String {
public:
String(const char *str = NULL) //str接收字符串首地址
{
TRACE()
int len = strlen(str) + 1; //计算字符串占空间大小
ptr = new char [len]; //给字符串开辟空间
assert(NULL != ptr);
strcpy(ptr, str); //将指定的字符串拷贝到ptr指向的堆区空间
}
~String()
{
TRACE()
delete [] ptr;
}
public:
//重载+
String operator + (const String &);
String & operator = (const String &); //a = b ; <=> a.operator = (b)
int operator == (const String &); //a == b; <=> a.operator == (b)
friend ostream & operator << (ostream &, const String &); //cout << a; <=> operator << (cout, a)
private:
char *ptr;
};
String String::operator + (const String &obj) //a + b <=> a.operator +(b)
{
TRACE()
int len = strlen(this->ptr) + strlen(obj.ptr) + 1;
char *p = new char [len];
assert(NULL != p);
strcpy(p, this->ptr);
strcat(p, obj.ptr);
String tmp(p);
delete [] p;
return tmp;
}
String & String::operator = (const String &obj) //a = b ; <=> a.operator = (b)
{
TRACE()
delete [] this->ptr;
int len = strlen(obj.ptr) + 1;
this->ptr = new char [len];
assert(NULL != ptr);
strcpy(this->ptr, obj.ptr);
return *this;
}
int String::operator == (const String &obj) // a == b; <=> a.operator == (b)
{
TRACE()
int ret = strcmp(this->ptr, obj.ptr);
return ((ret == 0) ? 1: 0);
}
ostream & operator << (ostream &os, const String &obj) //cout << a; <=> operator << (cout, a)
{
cout << __func__ << ":" << __LINE__ << endl;
os << obj.ptr;
return os;
}
int main()
{
String a = "hello ";
String b = "world";
cout << a + b << endl;
a = b;
cout << a << endl;
cout << (a == b) << endl;
return 0;
}
函数重载
函数重载:在同一个作用域下,函数名相同,参数不同【个数不同、类型不同、个数和类型都不同】
#include <stdio.h>
int add(int x)
{
return (x+x);
}
int add(int x, int y)
{
return (x+y);
}
double add(double x, double y, double z)
{
return (x+y+z);
}
int main()
{
printf("%d\n",add(10)); //根据实参自动适配函数 调用有一个int型参数的add函数
printf("%d\n",add(10,20)); //调用有两个int型参数的add函数
printf("%lf\n",add(1.1,2.2,3.3)); //调用有三个double型参数的add函数
return 0;
}
#include <stdio.h>
int add(int x,int y=0) //给形参y指定默认参数值为 0
{
return (x+y);
}
int add(int x, int y,int z=0)
{
return (x+y+z);
}
int main()
{
printf("%d\n",add(20)); //根据实参自动适配函数
//printf("%d\n",add(10,20));//有歧义 两个版本的add都适合
printf("%d\n",add(10,20,30)); //根据实参自动适配函数
return 0;
}
函数模板
指:函数返回值类型及参数类型为通用类型 T
eg:
template <typename T>
T add(T a, T b)
{
return (a+b);
}
总结:
函数模板是泛型编程在C++中的应用方式之一
-
函数模板能够【根据实参对参数类型进行推导】
-
函数模板支持显示的指定参数类型
-
函数模板是C++中重要的代码复用方式
-
函数模板通过具体类型产生不同的函数
-
函数模板可以定义任意多个不同的类型参数
-
函数模板中的返回值类型必须显示指定
-
函数模板可以像普通函数一样重载
-
注意:函数重载与函数模板的选择
- 当函数【行为相近、参数不同】时使用函数重载;
- 当函数【行为一致】,则需要使用函数模板(即函数逻辑一致,操作的数据类型不同时,使用模板);
函数模板跟普通函数一样,也可以被重载
- C++编译器优先考虑普通函数
- 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
- 也可以通过空模板实参列表<>限定编译器只匹配函数模板
模板
C++中模板是支持【参数化多态】的工具,就是让类
或者函数
声明为一种通用类型,使得类中的【某些数据成员】或者成【员函数的参数】、【返回值】在实际使用时可以是【任意类型】。使用模板的目的就是能够让程序员编写与类型无关的代码,模板也是泛型编程的基础。
模板是一种对类型进行参数化的工具,通常有两种形式:函数模板和类模板。
类型模板参数
模板形参
表示的是一个未知的类型。模板类型形参可作为【类型说明符】用在模板中的任何地方,与【内置类型说明符】或【类类型说明符】的使用方式完全相同,即可以用于【指定返回类型】,【变量声明】等。
类型形参
仅由关键字class
或typename
后接【说明符】构成
//譬如函数模板
template<class T>
T func(T var)
{
…
}
template< typename T>
T func(T var)
{
…
}
其中T就是一个类型形参,形参var及返回值类型为T,实际类型在实例化时确定。
函数模板
函数模板针对仅【参数类型不同】,但是【功能及函数名一致】的函数。
template <class 形参名,class 形参名,......>
返回类型 函数名(参数列表)
{
函数体
}
#include <iostream>
using namespace std;
//声明模板函数
template <typename T> //T:类型形参
T add(T a, T b);
//定义模板函数add,并指定模板函数中的通用类型为 T
template <typename T> //T:类型形参
T add(T a, T b) { return (a + b); }
int main()
{
cout << add(10,20) << endl; //根据实参类型自动匹配T 为 int
cout << add(10.1,20.2) << endl; //根据实参类型自动匹配T 为 float
//cout << add(10,20.2) << endl; //ERROR 类型不匹配
cout << add<int>(10,20) << endl; //在调用函数add时,指定类型实参为int
return 0;
}
-
注意:
模板的声明或
定义
【只能在全局】,【命名空间】或【类范围内】进行。
即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
#include <iostream>
using namespace std;
//声明模板函数
template <typename T> //T:类型形参
T add(T a, T b);
class Demo {
public:
Demo(int val) : myval(val) { TRACE() }
~Demo() { TRACE() }
Demo operator + (Demo &obj)
{
//TRACE()
#if 0
Demo tmp(this->myval + obj.myval);
return tmp;
#else
return Demo(this->myval + obj.myval);
#endif
}
friend ostream & operator << (ostream &, const Demo &);
private:
int myval;
};
ostream & operator << (ostream &os, const Demo &obj)
{
//TRACE()
os << obj.myval;
return os;
}
//定义模板函数add,并指定模板函数中的通用类型为 T
template <typename T> //T:类型形参
T add(T a, T b)
{
return (a + b);
}
int main()
{
Demo a(222);
Demo b(444);
#if 0
Demo c = add<Demo> (a,b); //T:既可以是基本类型,又可以是自定义类型,例如:Demo类型
cout << c << endl;
#else
cout << add<Demo>(a,b) << endl;
#endif
return 0;
}
类模板
类模板
针对【数据成员】和【成员函数】类型不同的类。
成员属性的类型和成员函数的类不一样,但是成员属性及函数一样的类。
//语法形式
template <typename T, ...., typename C>
class 类名
{
public:
T 方法名(T 参数名) { 语句块;}
private:
T 成员变量名;
};
eg:
//定义模板类 Demo
template <typename T>
class Demo {
public:
Demo(T val); //由于T是通用类型,所以构造函数不能指定默认参数值
~Demo();
public:
void setval(T val);
T getval() const; //声明成员方法
private:
T myval;
};
//在类外部定义/实现成员方法
template <typename T>
Demo<T>::Demo(T val)
{
TRACE()
myval = val;
}
template<typename T>
Demo<T>::~Demo() { TRACE() }
template<typename T>
void Demo<T>::setval(T val)
{
myval = val;
}
template<typename T>
T Demo<T>::getval() const
{
return myval;
}
nt main()
{
Demo<int> obj(123); //必须指定具体类型
cout << obj.getval() << endl;
return 0;
}
声明【数据类型参数标识符】的关键字既可以用class
也可以用typename
。
非类型模板形参
模板的非类型形参就是【内置类型形参】
//譬如:
template<class T, int var>
class Demo
{
…
};
其中
int var
就是非类型的模板形参;
非类型形参在模板定义的内部是常量值;
非类型模板的形参只能是整型、指针和引用
,像double,String, String **
这样的类型是不允许的。但是double &,double *
,【对象的引用】或【指针】是正确的;
非类型模板形参的实参如果是表达式,则必须是一个常量表达式,在编译时计算出结果;
非类型模板形参和实参间允许类型转换。
#include <iostream>
#include <assert.h>
using namespace std;
template <typename T = int, int len = 10> //T:类型模板形参,len:非类型模板形参
class Array{
public:
Array(T val)
{
TRACE()
ptr = new T [len]; //给数组在堆区申请空间
assert(NULL != ptr);
for(int i = 0; i < len; i++) //给数组中各个元素赋值
ptr[i] = val;
}
~Array()
{
TRACE()
delete [] ptr;
}
public:
void setval(const int pos, const T val);
T getval(int pos) const;
private:
T *ptr; //指向数组首元素,即数组首元素的指针
};
template <typename T, int len> //类外实现成员函数的定义
void Array<T,len>::setval(const int pos, const T val)
{
ptr[pos] = val;
}
template <typename T, int len>
T Array<T,len>::getval(int pos) const
{
return ptr[pos];
}
int main()
{
//定义模板类对象,当模板形参指定了默认参数时,定义对象时可以省略类型实参
Array<> obj(0);
Array <char, 10> obj1('\0');
int i = 0;
for(i=0;i<10;i++)
obj.setval(i, i+1);
for(i=0;i<10;i++)
cout << obj.getval(i) << " ";
cout << endl;
return 0;
}
默认类型模板参数
【类模板】的【类型形参】可以有默认值,【函数模板】的【类型形参】则不能;
【函数模板】和【类模板】都可以为模板的【非类型形参】提供默认值。
//类模板的类型形参默认值形式为:
template<class T1, class T2 = int>
class Demo
{
…
};
#include <iostream>
using namespace std;
template <typename T = int> //类型的默认参数值,当定义类对象时不指定类型实参时,默认T为int
class Demo {
public:
Demo(T val) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
T getval() const; //声明成员方法
private:
T myval;
};
template <typename T> //在类外部实现成员方法
T Demo<T>::getval() const
{
return this->myval;
}
int main()
{
Demo<> obj(123); //定义类对象,没有指定类型实参,则对象的T类型为默认类型int
cout << obj.getval() << endl;
return 0;
}
类型形参T1、T2,其中T2的默认类型为int;
类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从左起的某个形参设定了默认值之后,右边所有模板形参都要设定默认值;
和函数默认参数一样,如果在类模板的外部定义类中的成员时,模板形参表应省略默认的形参类型;
友元模板函数
如果一个类是模板类
,又要实现运算符重载
,一般的,运算符重载是友员函数
,那么显然会涉及到一个问题,一个【友员如何操作模板类】;
其实质就是类模板
和函数模板
的【综合应用】。
#include <iostream>
using namespace std;
template <typename T> class Demo {
public:
Demo(T val) : myval(val) { TRACE() }
~Demo() { TRACE() }
public:
T getval() const;
#if 0
Demo<T> operator + (Demo<T> & obj) //成员函数运算符重载
{
Demo<T> tmp(this->myval+obj.myval);
cout << &tmp << endl;
return tmp;
}
#else
//友元函数模板
template <typename C>
friend Demo <C> operator + (Demo <C> & , Demo<C> &);
#endif
private:
T myval;
};
template <typename T>
T Demo<T>::getval() const
{
return this->myval;
}
//运算符+重载
template <typename C>
Demo <C> operator + (Demo <C> & obj1, Demo<C> &obj2)
{
return Demo<C> (obj1.myval + obj2.myval);
}
int main()
{
Demo<int> a(222);
Demo<int> b(444);
Demo<int> c = a + b; //编译器优化,让c和tmp用同一个空间,提高效率
cout << &c << endl;
cout << c.getval() << endl;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ea4IKk4M-1629157866080)(https://i.loli.net/2021/08/11/OVhyvleTJ8NuPCq.png)]
类继承
在进行C
编程的时候,使用库函数是很常见的,这个就是代码重用
(有的技术文献翻译成代码复用
)。代码重用的目的就是提高开发效率、减少错误、让大规模代码开发的关注点转到软件结构上。C++(OOP)的代码重用除了简单层次的提供类库,还提出了更高层次的类继承(inheritance)
、多态(Polymorphism)
、泛型编程(Generic Programming)
等等。
在C++中,所谓“继承”
就是在一个已存在的类的基础上建立一个新的类。已存在的类(例如:“学生”)称为“基类(base class)”
或“父类(father class)”
。新建立的类(例如:“小学生”)称为“派生类(derived class)”
或“子类(son class)“
。
一个新类从已有的类那里【获得其已有特性】,这种现象称为类的继承
。通过继承
,一个新建子类从已有的父类那里获得父类的特性。从另一种角度说,从已有的类(父类)产生一个新的子类,称为类的派生
。
类的继承
是用已有的类来【建立专用类】的编程技术。
派生类
继承了基类
的所有【数据成员】和【成员函数】,并可以对成员作必要的增加或调整。
一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的。
关于基类和派生类的关系,可以表述为: 派生类是基类的【具体化】,而基类则是派生类的【抽象】。
派生一个类
- 语法如下:
class 子类名 : 权限访问限定符 基类名1,权限访问限定符 基类名2,...
{
//class body
};
-
权限访问限定符
:public
:表示基类
的public/protected成员
可以被子类
访问,通过子类对象
可以访问基类
的public成员
;protected/private
:表示基类
的public/protected
【成员】可以被子类
访问到,通过【子类对象】不能访问基类
任何成员。
eg:
//定义基类/父类
class Base {
public:
Base(int val=0) : myval(val) { TRACE() }
~Base() { TRACE() }
public:
int getval() const { return myval; }
protected:
void setval(int val) { myval = val; }
private:
int myval;
};
/*************************************************
public继承时:
基类public成员 --> 子类public成员
基类的protected成员 --> 子类的protected成员
基类的private成员:与子类无关,只能在基类内部访问
*************************************************/
//定义派生类/子类
class Inherit : public Base {
public:
Inherit()
{
setval(123); //子类内部访问基类的protected成员
cout << getval() << endl; //子类内部访问基类的 public成员
//myval++; //error 基类的private成员只能在基类内部访问,与子类无关
TRACE()
}
~Inherit() { TRACE() }
};
int main()
{
Inherit obj; //定义子类对象
//obj.setval(321); //error protected成员只能在基类内部或子类内部访问
cout << obj.getval() << endl;
return 0;
}
-
注意:
public继承时:
基类
public
成员 --> 子类public
成员
基类的protected
成员 --> 子类的protected
成员
基类的private
成员:与子类无关,只能在【基类内部访问】。
protected继承时:
基类
public
成员 --> 子类protected
成员
基类的protected
成员 --> 子类的protected
成员
基类的private
成员:与子类无关,只能在【基类内部访问】。
#include <iostream>
using namespace std;
//定义基类/父类
class Base {
public:
Base(int val=0) : myval(val) { TRACE() }
~Base() { TRACE() }
public:
int getval() const { return myval; }
protected:
void setval(int val) { myval = val; }
private:
int myval;
};
/*************************************************
protected继承时:
基类public成员 --> 子类protected成员
基类的protected成员 --> 子类的protected成员
基类的private成员:与子类无关,只能在基类内部访问
*************************************************/
//定义派生类/子类
class Inherit : protected Base {
public:
Inherit()
{
setval(123); //子类内部访问基类的protected成员
cout << getval() << endl; //子类内部访问基类的 protected成员
//myval++; //error 基类的private成员只能在基类内部访问,与子类无关
TRACE()
}
~Inherit() { TRACE() }
};
int main()
{
Inherit obj; //定义子类对象
cout << sizeof(obj) << endl;
//obj.setval(321); //error protected成员只能在基类内部或子类内部访问
//cout << obj.getval() << endl; //error protected成员只能在基类内部或子类内部访问
return 0;
}
private
继承时:
基类public
成员 --> 子类private
成员
基类的protected
成员 --> 子类的private
成员
基类的private
成员:与子类无关,只能在【基类内部】访问
#include <iostream>
using namespace std;
//定义基类/父类
class Base{
public:
Base(int val=0):myval(val) { TRACE() }
~Base() { TRACE() }
public:
int getval() const { return myval; }
protected:
void setval(int val) { myval = val; }
private:
int myval;
};
/*************************************************
private继承时:
基类public成员 --> 子类private成员
基类的protected成员 --> 子类的private成员
基类的private成员:与子类无关,只能在基类内部访问
*************************************************/
//定义派生类/子类
class Inherit : private Base {
public:
Inherit()
{
setval(123); //setval在子类的访问权限是 private,private成员可以在类内部访问
cout << getval() << endl; //getval在子类的访问权限是 private,private成员可以在类内部访问
//myval++; //error 基类的private成员只能在基类内部访问 与子类无关
TRACE()
}
~Inherit() { TRACE() }
};
class Subcls:private Inherit {
public:
Subcls()
{
setval(321);
cout << getval() << endl;
TRACE()
}
~Subcls() { TRACE() }
};
int main()
{
Subcls obj;//定义子类对象
return 0;
}
派生类访问基类的成员的权限如下表所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orf6nUzz-1629157866081)(https://i.loli.net/2021/08/03/LE7zdegTJGY1rV8.png)]
派生类的构造及析构函数
派生类
不能继承基类
的构造
、析构函数
;
派生类有自己的构造
、析构函数
;
如果基类构造函数
有参数,再从派生类
的构造函数
把参数传递给基类
的构造函数
。
//在类外定义派生类的构造函数
派生类名::构造函数名(参数列表) : 基类名(参数列表)
{
…
}
//eg:
//定义基类/父类
class Base{
public:
Base(int val):myval(val) { TRACE() }
~Base() { TRACE() }
public:
int getval() const { return myval; }
protected:
void setval(int val) { myval = val; }
private:
int myval;
};
/*************************************************
public继承时:
基类public成员 --> 子类public成员
基类的protected成员 --> 子类的protected成员
基类的private成员:与子类无关,只能在基类内部访问
*************************************************/
//定义派生类/子类
class Inherit:public Base {
public:
Inherit(int val):Base(val) //初始化列表初始化基类的构造函数的形参
{
setval(321); //子类内部访问基类的protected成员
cout << getval() << endl; //子类内部访问基类的 public成员
/myval++; //error 基类的private成员只能在基类内部访问 与子类无关
TRACE()
}
~Inherit() { TRACE() }
private:
int val;
};
int main()
{
Inherit obj(123); //定义子类对象
//obj.setval(321); //error protected成员只能在类内部或子类内部访问
cout << obj.getval() << endl;
return 0;
}
派生类的构造、析构函数和基类的构造析构函数的执行次序
派生类对象的生命期{
基类的构造函数
派生类的构造函数
…
派生类的析构函数
基类的析构函数
}
is-a关系
派生类
和基类
之间的关系基于C++继承的【底层模型】,公有继承
是最常见的一种继承方式,就是所谓的is-a
关系:派生类对象
也是一个基类对象
,基类对象
能够做的任何操作,派生类
对象也可以。
is-a
关系描述:水果是基类,苹果是一种派生类,苹果是一种特定的水果。但是存在另外一种聚合的情况,午餐有水果,午餐和水果本生没有直接联系,譬如午餐中包含苹果,这就是has-a
.所谓has-a
是一种聚合,表示一个类中有另外一个类的对象。
is-a
一般是继承
关系,has-a
一般是组合
关系。
公有继承
不能建立is-implemented-a
(作为…来实现)关系
设计一个类,【需要用到】另外一个类,而使用继承
来实现。
譬如,数组可以用来实现栈,那么,先抽象一个Array类,再从Array派生栈类Stack,这样是不适合的,因为栈不是数组。
公有继承不能建立is-like-a关系
设计一个类,既有A类的特性,也有B类的特性,不能使用继承。
譬如:一个人像狼一样,这样需要建立一个类,既有人的特性,也有狼的特性。那么不能使用is-a
设计,因为人不是狼,狼也不是人。也不能使用has-a
来实现,因为不是每个人都有狼的特性。
多重继承
讨论继承的时候强调了是is-a,那么就可能出现如下的情况:描述一个人像一匹狼一样,那么这个人的抽象类就应该具有人和狼的两个特性。如果直接设计一个类抽象人和狼,那么就不是is-a的继承关系了。解决这个问题的办法可以使用多重计继承。
多重继承就是一个派生类有多个直接继承的基类,是单一继承的一种扩展,派生类与每个基类之间的关系仍可看作是一个单继承。
基本语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
#include <iostream>
using namespace std;
//构造wolf类
class wolf{
public:
wolf() { TRACE() }
~wolf() { TRACE() }
public:
void prnmsg() { TRACE() }
};
//构造man类
class man{
public:
man() { TRACE() }
~man() { TRACE() }
public:
void prnmsg() { TRACE() }
};
//构造wolfman
//多重继承
class wolfman : public man, public wolf
{
public:
wolfman() { TRACE() }
~wolfman() { TRACE() }
};
int main()
{
wolfman obj;
obj.prnmsg();
return 0;
}
多重继承的构造与析构函数执行次序
派生类的生命期{
左起第一个基类的构造函数
左起第二个基类的构造函数
…
派生类的构造函数
…
派生类的析构函数
…
左起第二个基类的析构函数
左起第一个基类的析构函数
}
#include <iostream>
using namespace std;
class sw{
public:
sw() { TRACE() }
~sw() { TRACE() }
public:
void prnmsg() { TRACE() }
};
class man:public sw {
public:
man() { TRACE() }
~man() { TRACE() }
};
class wolf:public sw {
public:
wolf() { TRACE() }
~wolf() { TRACE() }
};
//多重继承
class wolfman:public man, public wolf{
public:
wolfman() { TRACE() }
~wolfman() { TRACE() }
};
int main()
{
wolfman obj;
obj.prnmsg(); //多重继承产生歧义
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public: //公有成员方法
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public: //公有成员函数
int getval() const
{
return this->myval;
}
private: //私有成员变量
int myval;
};
/*****************************************************************************************************
*protected继承时:
* 父类 子类
* public成员 protected成员 在类内部或子类内部访问
* protected成员 protected成员 在类内部或子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
* *******************************************************************************************************/
//子类protected继承基类Base
class Inherit : protected Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : val(val),Base(321)
{
TRACE()
this->val = val;
//myval++; //ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
//setval(666); //protected成员只能在类内部或者子类内部访问
//cout << getval() << endl; //protected 成员只能在类内部或子类内部访问
}
~Inherit() { TRACE() }
private:
int val;
};
//prviate 继承:
//setval() //private方法
//getval() //private方法
class Subcls : private Inherit
{
public:
Subcls() : Inherit(0)
{
TRACE()
setval(777); //private成员只能在类内部访问
cout << getval() << endl; //private成员只能在类内部访问
}
~Subcls() { TRACE() }
};
int main()
{
Subcls *p = new Subcls; //定义子类对象obj
//p->setval(666); //ERROR protected成员只能在类内部访问或者子类内部访问
//ERROR protected继承时,基类的public成员方法 getval() 在子类的访问权限是protected权限,只能在类内部或子类内部访问
//cout << p->getval() << endl;
delete p;
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public: //公有成员方法
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public: //公有成员函数
int getval() const
{
return this->myval;
}
private: //私有成员变量
int myval;
};
/*****************************************************************************************************
*protected继承时:
* 父类 子类
* public成员 protected成员 在类内部或子类内部访问
* protected成员 protected成员 在类内部或子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
*
*******************************************************************************************************/
//getval() private
//setval() private
class Inherit : private Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : val(val),Base(321)
{
TRACE()
this->val = val;
//myval++; //ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
//setval(666); //protected成员只能在类内部或者子类内部访问
//cout << getval() << endl; //protected 成员只能在类内部或子类内部访问
}
~Inherit() { TRACE() }
private:
int val;
};
//prviate 继承:
//setval() //是Inherit的private方法,在Inherit类内部访问
//getval() //是Inherit的private方法,在Inherit类内部访问
class Subcls : private Inherit
{
public:
Subcls() : Inherit(0)
{
TRACE()
//setval(777); //ERROR setval是inhrit的私有成员,只能在Inherit类内部访问
}
~Subcls() { TRACE() }
};
int main()
{
Subcls *p = new Subcls; //定义子类对象obj
delete p;
return 0;
}
#include <iostream>
using namespace std;
class Base {
public:
Base(int val = 321) : myval(val) { TRACE() }
~Base() { TRACE() }
public:
int getval() const
{
return this->myval;
}
private:
int myval;
};
class Inherit : public Base
{
public:
Inherit(int val) :myval(val)
{
TRACE()
cout << Base::getval() << endl; //访问父类的成员方法
}
~Inherit() { TRACE() }
public:
//当子类和父类方法相同时,子类隐藏父类方法
int getval() const
{
return myval;
}
private:
int myval;
};
int main()
{
Inherit obj(123); //Base类对象 Inherit对象
//cout << obj.getval() << endl;
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public: //公有成员方法
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public: //公有成员函数
int getval() const
{
return this->myval;
}
private: //私有成员变量
int myval;
};
/*****************************************************************************************************
*private继承时:
* 父类 子类
* public成员 pivate成员 在子类内部访问
* protected成员 private成员 在子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
*
*******************************************************************************************************/
//子类private继承基类Base
class Inherit : private Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : myval(val),Base(321)
{
TRACE()
this->myval = val;
//myval++; //ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
this->setval(666); //private成员只能在子类内部访问
cout << getval() << endl; //private 成员只能在子类内部访问
cout << this->myval << endl;
}
~Inherit() { TRACE() }
private:
int myval;
};
int main()
{
Inherit *p = new Inherit(123); //定义子类对象obj
//p->setval(666); // ERROR private成员只能在子类内部访问
//ERROR private继承时,基类的public成员方法 getval() 在子类的访问权限是private权限,只能在子类内部访问
//cout << p->getval() << endl;
delete p;
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public: //公有成员方法
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public: //公有成员函数
int getval() const
{
return this->myval;
}
private: //私有成员变量
int myval;
};
/*****************************************************************************************************
*protected继承时:
* 父类 子类
* public成员 protected成员 在类内部或子类内部访问
* protected成员 protected成员 在类内部或子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
* *******************************************************************************************************/
//子类protected继承基类Base
class Inherit : protected Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : val(val),Base(321)
{
TRACE()
this->val = val;
//myval++;//ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
setval(666);//protected成员只能在类内部或者子类内部访问
cout << getval() << endl;//protected 成员只能在类内部或子类内部访问
}
~Inherit() { TRACE() }
private:
int val;
};
int main()
{
Inherit *p = new Inherit(123); //定义子类对象obj
//p->setval(666); // ERROR protected成员只能在类内部访问或者子类内部访问
//ERROR protected继承时,基类的public成员方法 getval() 在子类的访问权限是protected权限,只能在类内部或子类内部访问
//cout << p->getval() << endl;
delete p;
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public:
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public: //公有成员函数
int getval() const
{
return this->myval;
}
private:
int myval;
};
/*****************************************************************************************************
*public继承时:
* 父类 子类
* public成员 public成员 可在类外部使用类对象或对象指针访问
* protected成员 protected成员 在类内部或子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
* *******************************************************************************************************/
//子类public继承基类Base
class Inherit : public Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : val(val),Base(321)
{
TRACE()
this->val = val;
//myval++; //ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
setval(666); //protected成员只能在类内部或者子类内部访问
cout << getval() << endl; //public成员既可以在类内部方法,也可以在类外部通过对象或对象指针访问
}
~Inherit() { TRACE() }
private:
int val;
};
int main()
{
Inherit *p = new Inherit(123); //定义子类对象obj
//p->setval(666); // ERROR protected成员只能在类内部访问或者子类内部访问
cout << p->getval() << endl; //结果:321 子类对象指针访问从父类继承的public
delete p;
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base {
public:
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected: //保护成员函数
void setval(const int val)
{
this->myval = val;
}
public://公有成员函数
int getval() const
{
return this->myval;
}
private:
int myval;
};
/*****************************************************************************************************
*public继承时:
* 父类 子类
* public成员 public成员 可在类外部使用类对象或对象指针访问
* protected成员 protected成员 在类内部或子类内部访问
* private成员 只能通过父类的成员方法访问,与子类无关
*
*******************************************************************************************************/
//子类public继承基类Base
class Inherit : public Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : myval(val),Base(321)
{
TRACE()
this->myval = val;
//myval++; //ERROR 基类私有的成员只能通过基类成员方法访问,与子类无关
setval(666); //protected成员只能在类内部或者子类内部访问
cout << getval() << endl; //public成员既可以在类内部方法,也可以在类外部通过对象或对象指针访问
cout << this->myval << endl;
}
~Inherit() { TRACE() }
public:
void setval(const int val)
{
this->myval = val;
}
private:
int myval;
};
int main()
{
Inherit obj(123); //定义子类对象obj
cout << sizeof(obj) << endl;
cout << obj.getval() << endl; //结果:321,子类对象指针访问从父类继承的public
return 0;
}
#include <iostream>
using namespace std;
//基类/父类
class Base{
public:
Base(int val) : myval(val) { TRACE() }
~Base() { TRACE() }
protected:
void setval(const int val) { this->myval = val; }
public:
int getval() const
{
return this->myval;
}
private:
int myval;
};
//子类public继承基类Base
class Inherit : public Base
{
public:
//当基类的构造函数有参数时,并且参数无默认参数值时,必须通过子类的初始化列表给基类构造函数传实参
Inherit(int val) : val(val),Base(321)
{
TRACE()
this->val = val;
//myval++; //基类私有的成员只能通过基类成员方法访问,与子类无关
setval(666); //protected成员只能在类内部或者子类内部访问
cout << getval() << endl; //public成员既可以在类内部方法,也可以在类外部通过对象或对象指针访问
}
~Inherit() { TRACE() }
private:
int val;
};
int main()
{
Inherit *p = new Inherit(123); //定义子类对象obj
//p->setval(666); // ERROR protected成员只能在类内部访问或者子类内部访问
cout << p->getval() << endl; //结果:321 子类对象指针访问从父类继承的public
delete p;
return 0;
}
多重继承
必须要慎重使用,譬如抽象一个worker类,signer及waiter从worker派生,singingwriter从singer和waiter派生。这样情况下,在singingwaiter派生类里面包含几个worker呢?
多重继承可能引发使用基类成员函数出现二义性。
多态
多态
(Polymorphism
)按字面的意思就是“多种状态”,简单地概括为“一个接口,多种方法
”,程序在运行时才决定调用的函数,是面向对象
编程领域的核心概念。
即多态为:一种方法,多种实现
;
多态性是【将接口与实现进行分离】;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。
在面向对象编程(OOP)的主要特征:
封装
(wrap):实现细节隐藏,使得代码【模块化】。把成员数据和成员函数封装起来,通过【公共的成员接口】进行成员数据的操作;
继承
(inheritance):扩展已存在的代码,目的是为了代码重用;
多态
(polymorphism):目的是为了接口重用。也就是说,不论传递过来的究竟是哪个类的对象,函数都能够通过【同一个接口】调用到适应各自对象的实现方法。
虚函数
虚函数(virtual function)
有时候,希望子类
和基类
有【相同的方法】,但是【行为却有所不同】,这就是多态
。这里就引入了虚函数
的概念。
-
注意:简单
继承
,is-a的关系不是多态
;另外函数重载
,一般应该是【行为类似】,使用【方法不同】,也不是多态
。简单地说,用
virtual
修饰的成员函数
,就是虚函数
。虚函数
的作用就是【实现多态性】(Polymorphism)。虚函数的限制如下:
A、【非类的成员函数】不能定义为
虚函数
;B、【类的静态成员函数】不能定义为
虚函数
;C、【构造函数】不能定义为
虚函数
,但可以将【析构函数】定义为虚函数
;D、只需要在声明函数的类体中使用关键字“
virtual
”将函数声明为虚函数
,而定义函数时不需要使用关键字“virtual
”;E、当将【基类】中的某一成员函数声明为
虚函数
后,【派生类】中的【同名函数】(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数
。
虚函数语法
class 类名 {
public:
virtual 返回值类型 函数名1(参数列表)
{
}
...
virtual 返回值类型 函数名n(参数列表)
{
}
};
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class Base {
public:
Base() { TRACE() }
~Base() { TRACE() }
public:
virtual void prnmsg(void) const //虚函数
{
TRACE()
}
};
class Inherit : public Base {
public:
Inherit()
{
//prnmsg(); //prnmsg是子类的方法,子类方法会屏蔽基类同名方法
TRACE()
}
~Inherit() { TRACE() }
public:
void prnmsg(void) const //自动变为虚函数,子类prnmsg覆盖了基类的prnmsg
{
TRACE()
}
};
void test(Base &obj)
{
//虚函数在运行时根据【对象】决定调用基类的prnmsg还是子类的prnmsg,若对象是Base对象,则调用基类的prnmsg,若对象是子类对象,则调用子类的prnmsg
obj.prnmsg();
}
int main()
{
Base obj1;
Inherit obj2;
test(obj1);
test(obj2); //Inherit --> Base
return 0;
}
// Created by 程岛主 on 2021/8/8.
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class Base {
public:
virtual void fun(void);
};
void Base::fun(void) { TRACE() }
class Inherit : public Base {
public:
void fun(void);
};
void Inherit::fun(void) { TRACE() }
int main(int argc,char *argv[])
{
Base *p;
Base obj1;
Inherit obj2;
p = &obj1;
p->fun();
p = &obj2;
p->fun();
return 0;
}
覆盖、重载及隐藏
-
成员函数覆盖
(override
,也称重写
)是指派生类
【重新定义】基类
的虚函数
,特征如下:A、不同的作用域(分别位于派生类与基类);
B、函数名字相同;
C、参数相同;
D、
基类
必须有virtual
关键字,不能有static
;E、返回值相同;
F、重写函数的【权限访问限定符】可以不同。
-
成员函数重载
(overload
)是指【函数名相同,参数不同】(数量、类型、次序),特征如下:A、相同的范围(在同一个作用域中);
B、函数名字相同;
C、参数不同;
D、
virtual
关键字可有可无;E、返回值可以不同。
-
成员函数隐藏(也称
重定义
)A、不在同一个作用域(分别位于
派生类
与基类
);B、函数名字相同;
C、返回值可以不同;
D、参数不同。此时,不论有无
virtual
关键字,基类的函数将被隐藏(注意与重载的区别);E、参数相同。但是
基类函数
没有virtual
关键字。此时,基类的函数被隐藏(注意与覆盖的区别)。#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl; #include <iostream> using namespace std; class Base { public: virtual void func(void) { TRACE() } int func(int val) { TRACE() } }; class Inherit : public Base { public: void func(void) { TRACE() } int func(int val) { TRACE() } }; int main(int argc,char *argv[]) { Inherit obj; obj.func(); obj.func(1); return 0; }
派生类Inherit和基类Base都各自重载了func成员函数;
派生类Inherit的void func(void)成员函数覆盖了基类的void func(void)成员函数;
派生类Inherit的void func(void)成员函数隐藏了基类的void func(void)成员函数;
派生类Inherit的int func(int)成员函数覆盖了基类的int func(int)成员函数。
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
/**************************************************
1与2关系:重载
3与4关系:重载
1与3关系:3覆盖1
2与4关系:(隐藏)子类隐藏基类
***************************************************/
class Base {
public:
Base() { TRACE() }
~Base() { TRACE() }
public:
virtual void prnmsg(int i) const { TRACE() } //虚函数(1)
void prnmsg() const { TRACE() } //(2)
};
class Inherit : public Base {
public:
Inherit()
{
//prnmsg(); //prnmsg是子类的方法 子类方法会屏蔽基类同名方法
TRACE()
}
~Inherit() { TRACE() }
public:
void prnmsg(int i) const { TRACE() } //自动变为虚函数,子类prnmsg覆盖了基类的prnmsg,(3)
void prnmsg() const { TRACE() } //(4)
};
void test(Base &obj)
{
//虚函数在运行时根据对象决定调用基类的prnmsg还是子类的prnmsg,若对象是Base对象,则调用基类的prnmsg,若对象是子类对象,则调用子类的prnmsg
obj.prnmsg(123);
}
int main()
{
Base obj1;
Inherit obj2;
test(obj1);
test(obj2); //Inherit --> Base
return 0;
}
#include <iostream>
using namespace std;
/***********************************************************************
1,2关系:重载关系,因为同一个作用域,同名,参数不同
3,4关系:重载关系
1,3关系:覆盖(重写)关系,因为作用域不同,同名、参数相同、返回值相同,基类中有virtual
2,4关系:隐藏关系,因为作用域不同,同名、参数相同,并且基类中无virtual
************************************************************************/
class Base{
public:
Base() { TRACE() }
~Base() { TRACE() }
public:
virtual void fun() const { TRACE() } //(1)
void fun(const int i) const { TRACE() } //(2)
};
class Inherit : public Base {
public:
Inherit()
{
TRACE()
//默认调用子类的fun方法,因为子类fun(const int i)屏蔽基类的fun(const int i)
fun(321);
Base::fun(666); //调用基类的fun方法
}
~Inherit() { TRACE() }
public:
void fun() const { TRACE() } //(3)
void fun(const int i) const { TRACE() } //(4)
};
int main()
{
Base *p = NULL;
Inherit obj;
p = &obj;
p->fun(); //调用子类的虚函数fun方法,虚函数在运行时由对象决定
p->fun(123); //调用基类的fun方法,非虚函数在编译时由类型决定
return 0;
}
- 注意:(重点)
虚函数
是在运行时根据对象决定调用的函数;
动态联编
-
联编(链接)
就是将【模块或者函数合并】在一起生成【可执行代码】的处理过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:【静态联编】和【动态联编】。 -
静态联编(静态链接):
是指在【编译阶段】就将【函数实现】和【函数调用】关联起来,因此静态联编也叫【早绑定】。 -
动态联编(动态链接):
是指在程序【执行】的时候才将【函数实现】和【函数调用】关联,因此也叫【运行时绑定】或者【晚绑定】。C++中一般情况下联编也是【静态联编】,但是一旦涉及到多态
和虚函数
就必须要使用【动态联编】了。 -
重载只是一种语言特性,编译器根据函数不同的参数表,把【同名函数】区分开来,属于【静态联编】,与
多态
无关。引用一句Bruce Eckel
的话:“不要犯傻,如果它不是【晚绑定】,它就不是多态
。”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8lHhRFgI-1629157866083)(https://i.loli.net/2021/08/08/RUxPXC2hH8YaOJy.png)]
抽象类
一般的,使用一个类,仅仅关心public
成员,故此需要隐藏类的其他的成员的方法。另外的,譬如设计圆和椭圆的基类,遇到的问题是,圆关心的是半径,而椭圆则有长半径和短半径,如果简单继承
,并不能解决问题。雷同的问题是:很多时候基类
本身生成对象不合理,例如,动物作为一个基类
可以派生出老虎、孔雀等子类,但动物本身并不能作为任何实际明确的对象
。
为了解决这些问题,C++
引入了纯虚函数
。语法如下:
virtual ReturnType Function(Argument List) = 0;
有纯虚函数
的类叫抽象类
;(除纯虚析构
要声明定义外)
语法形式
class 类名 {
public:
virtual 返回值类型 函数名1(参数列表) = 0; //只声明不定义
...
protected:
virtual 返回值类型 函数名n(参数列表) = 0; //只声明不定义
private:
virtual 返回值类型 函数名n(参数列表) = 0; //只声明不定义
};
eg:
class Base { //Base是抽象类
public:
void prnmsg(void) = 0; //prnmsg是纯虚函数 只声明不定义,必须在子类内部实现
};
纯虚函数
,【只声明,不定义,在子类内部实现】。
抽象类
不能定义对象
。
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
//Base是抽象类,因为Base类中有纯虚函数
class Base {
public:
Base(){ TRACE() }
~Base() { TRACE() }
public:
virtual void prnmsg(int i) const = 0; //纯虚函数,只声明,不定义,在子类内部实现
void prnmsg() const { TRACE() } //2
};
class Inherit : public Base {
public:
Inherit()
{
//prnmsg(); //prnmsg是子类的方法,子类方法会屏蔽基类同名方法
TRACE()
}
~Inherit() { TRACE() }
public:
void prnmsg(int i) const { TRACE() } //子类实现基类的方法
void prnmsg() const { TRACE() } //4
};
void test(Base &obj)
{
obj.prnmsg(123);//虚函数在运行时根据对象决定调用基类的prnmsg还是子类的prnmsg,若对象是Base对象,则调用基类的prnmsg,若对象是子类对象,则调用子类的prnmsg
}
int main()
{
//Base obj1; //error Base是抽象类,不能定义对象
#if 1
Inherit obj2; //right Inherit实现了基类的方法,不是抽象类,可以定义对象
test(obj2); //Inherit --> Base
#endif
return 0;
}
抽象基类
含有
纯虚函数
的类就是抽象类
;
抽象类
没有完整的信息,只能是派生类
的基类
;
抽象类
不能有实例
,不能有静态成员
;
派生类
应该实现抽象类
的所有方法
。
虚继承
虚继承
解决【多重继承】产生的【二义性】;
C++
使用虚继承(Virtual Inheritance)
,解决从【不同途径继承】来的【同名的数据成员】在内存中有不同的拷贝造成数据不一致问题,将【共同基类】设置为虚基类
。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class sw {
public:
explicit sw() { TRACE() }
virtual ~sw() { TRACE() }
public:
virtual void prnmsg() { TRACE() }
};
class man : public virtual sw { //虚继承
public:
explicit man() { TRACE() }
virtual ~man() { TRACE() }
};
class wolf : virtual public sw { //虚继承
public:
explicit wolf() { TRACE() }
virtual ~wolf() { TRACE() }
};
//多重继承
//构造wolfman
//多重继承:产生歧义/二义性 ,通过虚继承解决歧义
class wolfman : public man, public wolf { //由于man和wolf是虚继承sw,所以wolfman只继承一份sw
public:
explicit wolfman() { TRACE() }
virtual ~wolfman() { TRACE() }
};
int main()
{
wolfman obj;
obj.prnmsg(); //虚继承解决多重继承产生的二义性,不会有歧义,因为基类的基类是虚基类
return 0;
}
虚析构函数
析构函数
可以是虚函数
,而且一般的建议是析构函数
是虚函数
。
由于【指针指向的对象】是【基类类型】,所以delete
销毁对象的时候,并不会调用【派生类】的析构函数
,这样就造成了销毁对象销毁不完整。
一般将析构函数
指定为虚的,主要是避免空间回收不完整;
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class Base{
public:
Base() { TRACE() }
virtual ~Base() { TRACE() } //虚析构
};
class Inherit : public Base {
public:
Inherit() { TRACE() }
~Inherit() { TRACE() } //自动为虚析构
};
int main()
{
Base *p = new Inherit; //new 子类对象
//由于析构是虚函数,所以在析构时根据对象决定调用子类的析构,同时在析构子类对象时,会析构基类对象
delete p;
return 0;
}
如果基类
并不需要回收清理什么。那么析构函数
就可以是纯虚函数
。
不过纯虚析构函数
跟普通的纯虚函数
还有一点区别,不仅仅是声明,必须要有定义。【派生类】的析构函数
运行完成后会自动调用其【基类】的析构函数
。这个过程是【递归】的,最终,【抽象类】的纯虚析构
函数也会被调用。如果纯虚析构函数
只被声明而没有定义,那么就会造成运行时(runtime)崩溃。当然,很多优秀的C++编译器在编译时就不允许这样的情况发生。
纯虚析构函数
的【哑元实现】(dummy implementation,即空实现)能够保证这样的代码的安全性。
限制构造函数
指一个类的构造函数
的【权限】不是publlic,
而是protected/private
;
友元函数解决限制构造函数【不能构造对象】的问题。
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class Base {
protected: //限制构造
Base() { TRACE() }
~Base() { TRACE() }
public:
void prnmsg(void) const { TRACE() }
};
class Inherit : public Base {
public:
Inherit() { TRACE() }
~Inherit() { TRACE() }
public:
void prnmsg(void) const { TRACE() }
};
int main()
{
//Base obj1; //error 因为基类的构造权限是protected,不能构造对象
Inherit obj2; //right 子类对象可以访问基类的protected成员
return 0;
}
#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
class Base {
public:
Base(int val = 0) : myval(val) { TRACE() }
~Base() { TRACE() }
public:
virtual void fun(); //虚函数 --> 实现多态
private:
int myval;
};
void Base::fun() { TRACE() }
//派生类/子类 Inherit 公有继承 Base
class Inherit : public Base {
public:
Inherit(int val = 0) : Base(val) { TRACE() } //初始化列表给基类构造函数传递实参
~Inherit() { TRACE() }
public: // 成员方法
//与基类的fun是同名且参数列表一致,即子类重写基类的fun方法,子类的fun自动为虚函数
void fun() { TRACE() }
};
//由于fun是虚函数,运行时由对象决定调用的函数,所以,传过来的对象是Base对象,就调用Base类对象的fun方法,如果是Inherit对象,test中调用的就是Inherit类对象的fun方法
void test(Base &obj)
{
obj.fun();
return ;
}
int main()
{
Base obj1; //基类对象obj1
Inherit obj2; //子类对象obj1 构造的顺序:Base对象 --> Inherit对象
test(obj1); //Base的fun成员
test(obj2); //Inherit的fun成员 Inherit --> Base 的隐式类型转换
return 0;
}
#include <iostream>
using namespace std;
class Base{
protected: //限制构造函数:限制构造对象
Base() { TRACE() }
public:
virtual ~Base() { TRACE() } //虚析构
};
class Inherit : public Base //公有继承Base
{
public:
Inherit() { TRACE() }
public:
~Inherit() { TRACE() } //自动为虚析构
};
int main()
{
//Base obj; //ERROR Base类的构造函数权限是protected权限
Inherit obj; //right Inherit的构造函数是public权限的
return 0;
}
友元生成对象的例子:
#include <iostream>
#define TRACE() cout << typeid(this).name() << ":" << __func__ << ":" << __LINE__ << endl;
using namespace std;
class Base {
private: //限制构造函数:限制构造对象
Base() { TRACE() }
public:
virtual ~Base() { TRACE() } //虚析构
friend Base * getobj(); //声明友元函数
friend void freeobj(Base *p); //声明友元函数,可以访问类的私有成员
};
Base * getobj()
{
Base *p = new Base;
return p;
}
void freeobj(Base *p) { delete p; }
int main()
{
Base *p = getobj();
freeobj(p);
return 0;
}
#include <iostream>
using namespace std;
class Base {
public:
Base() { TRACE() }
~Base() { TRACE() }
public:
virtual void prnmsg() const { TRACE() }
};
class Inherit : public Base
{
public:
Inherit() { TRACE() }
~Inherit() { TRACE() }
public:
void prnmsg() const { TRACE() } //子类重写基类Base的方法prnmsg,并且子类prnmsg()方法自动为virtual
};
int main()
{
Base *p = NULL;
Base obj;
Inherit obj1;
p = &obj;
p->prnmsg(); //访问基类的虚函数表,所以调用的是基类Base的虚函数prnmsg()方法
cout << sizeof obj << endl;
// Inherit * --> Base * 隐式类型转换,访问子类Inherit的虚函数表,所以调用的是子类Inherit的序函数prnmsg()方法
p = &obj1;
cout << sizeof obj << endl;
p->prnmsg();
return 0;
}
成员函数属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fh1GG9vH-1629157866084)(https://i.loli.net/2021/08/07/V5xiyGWTodmEe4w.png)]
异常
将问题检测和问题处理相分离。
在执行程序发生异常时,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级函数来解决,上一级解决不了,再传给上一级,由其上一级处理,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate,由它调用abort终止程序。这样机制就是异常引发和处理机制分离。这使得底层函数只需要解决实际的任务,而不必过多考虑对异常的处理,而把异常处理的任务交给上一层函数去处理。
-
基本思想是:
让一个函数在发现了自己无法处理的错误时抛出(throw
)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。 -
C++
的异常处理机制有3部分组成:
try(检查错误) --> throw(抛出异常) --> catch(捕获异常)
-
一般的,throw抛出的异常要和catch所捕获的异常类型匹配,异常处理的一般格式:
try{
//检查错误并抛出错误
if(检错)
throw 异常1;
if(检错)
throw 异常2;
....
if(检错)
throw 异常n
} catch(异常1) {
处理异常
} catch(异常2) {
处理异常
}
...
catch(异常n)
{
处理异常
}
标准异常
C++标准异常(需要包含头文件stdexcept.h)
所谓标准异常的实质就C++已经定义好很多类,当错误出现的时候可以报告相应的标准错误信息,类似于Linux系统下的C库函数perror:
//C++98、C++11
class exception {
public:
exception () noexcept;
exception (const exception&) noexcept;
exception& operator= (const exception&) noexcept;
virtual ~exception();
virtual const char* what() const noexcept;
}
其中派生类的类名表示了标准的错误,而what可以打印用户指定的信息,当然也可以重写。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZlLfM8e-1629157866086)(https://i.loli.net/2021/08/08/rZIhlbsxWFAfeLd.png)]
//exception
#include <iostream>
#include <stdexcept>
using namespace std;
int mydiv(int x, int y)
throw (invalid_argument) //指定函数可以抛出 invalid_argument 异常
{
#if 0
if(0 == y)
return -1;//不合理
#endif
if(y != 0)
return x / y;
else // y == 0 异常
#if 1
//抛出异常invalid_argument,并指定异常原因为 "y == 0"
throw invalid_argument("y == 0");
#else
throw invalid_argument tmp("y == 0");
#endif
}
int main()
{
int a = 10;
int b = 0;
try {
int ret = mydiv(a,b);//调用函数mydiv
}
catch(invalid_argument &iserr)//捕获异常,并将异常取别名为iserr
{
cout << iserr.what() << endl;//打印异常原因
}
return 0;
}
自定义异常
在设计一个大系统的时候,往往设计者会自定义很多错误,这些错误在标准错误里面是没有的,那么就需要我们来设计一些异常类。
当然可以从标准异常exception类派生出来,也可以完全自定义一个异常类。
#include <iostream>
#include <stdexcept>
using namespace std;
//从标准异常派生出自定义异常 myexception异常
class myexception : public exception {
public:
myexception(const char *str) noexcept : ptr(str)
{
}
virtual ~myexception() noexcept //noexcept:指定当前异常方法不再抛出异常
{
}
const char *what() const noexcept //重写基类exception 的 what方法
{
return ptr;
}
private:
const char *ptr; //保存错误信息首地址
};
int mydiv(int x, int y)
throw (myexception) //指定函数可以抛出自定义异常 myexception 异常
{
#if 0
if(0 == y)
return -1; //不合理
#endif
if(y != 0)
return x / y;
else // y == 0 异常
throw myexception("y == 0"); //抛出自定义异常 myexception,并指定异常原因为 "y == 0"
}
int main()
{
int a = 10;
int b = 0;
try{
int ret = mydiv(a,b); //调用函数mydiv
}
catch(myexception &iserr) //捕获异常,并将异常取别名为iserr
{
cout << iserr.what() << endl; //打印异常原因
}
return 0;
}
#include <iostream>
#include <stdexcept>
using namespace std;
//编译时指定: -std=c++x
//自定义异常类 myexception异常
class myexception{
public:
myexception(const char *str) noexcept : ptr(str) // 构造函数
{
}
virtual ~myexception() noexcept //noexcept:指定当前异常方法不再抛出异常
{
}
const char *what() const noexcept //定义what方法
{
return ptr; //返回异常原因的首地址
}
private:
const char *ptr; //保存错误信息首地址
};
int mydiv(int x, int y)
throw (myexception) //指定函数可以抛出自定义异常 myexception 异常
{
#if 0
if(0 == y)
return -1; //不合理
#endif
if(y != 0)
return x / y;
else //y == 0 异常
//抛出自定义异常 myexception,并指定异常原因为 "y == 0"
throw myexception("invalid argument");
}
int main()
{
int a = 10;
int b = 0;
try {
int ret = mydiv(a,b); //调用函数mydiv
}
catch(myexception &iserr) //捕获异常,并将异常取别名为iserr
{
cout << iserr.what() << endl; //打印异常原因
}
return 0;
}
#include <iostream>
#include <stdexcept>
using namespace std;
//编译时指定: -std=c++x
//自定义异常类 myexception异常
class myexception {
public:
myexception(const char *str) noexcept : ptr(str) //构造函数
{
}
virtual ~myexception() noexcept //noexcept:指定当前异常方法不再抛出异常
{
}
const char *what() const noexcept //定义what方法
{
return ptr; //返回异常原因的首地址
}
private:
const char *ptr; //保存错误信息首地址
};
int mydiv(int x, int y)
//指定函数可以抛出标准异常 invalid_argument 和 自定义异常 myexception 异常
throw (invalid_argument, myexception)
{
#if 0
if(0 == y)
return -1; //不合理
#endif
if(0 == y)
throw invalid_argument("y == 0"); //抛出标准异常 invalid_argument,并指定异常原因 "y == 0"
else if(0 == x) // x == 0 异常
throw myexception("x == 0"); //抛出自定义异常 myexception,并指定异常原因为 "x == 0"
else //正常
return x / y;
}
int main()
{
int a = 0;
int b = 0;
try {
cout << mydiv(a,b) << endl; //调用函数mydiv
}
catch(myexception &iserr) //捕获自定义异常 myexception,并将异常取别名为iserr
{
cout << iserr.what() << endl; //打印异常原因
}
catch(invalid_argument &iserr) //捕获标准异常 invalid_argument
{
cout << iserr.what() << endl; //打印异常原因
}
return 0;
}
异常规范
异常规范是在C++11中弃用的C++语言功能。这些规范原本用来提供有关可以从函数引发哪些异常的摘要信息,但在实际应用中发现这些规范存在问题。证明确实有一定用处的一个规范是throw()规范。
例如:
void MyFunction(int i) throw;
告诉编译器函数不引发任何异常。它相当于使用nothrow。这种用法是可选的。(C++11)在ISO C++11标准中,引入了noexcept运算符,尽可能使用noexcept指定函数是否可能会引发异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcybKRMU-1629157866086)(https://i.loli.net/2021/08/08/Z146HsrOkuymFbn.png)]
eg:
//自定义异常 myexception
class myexception{
public:
myexception(const char *str) noexcept : ptr(str)
{
}
~myexception() noexcept
{
}
public:
const char *what() const noexcept
{
return (this->ptr);
}
private:
const char *ptr; //保存异常原因
};
int mydiv(int x, int y)
throw (myexception)
{
if(0 != y)
return (x/y);
throw myexception("invalid argument");
}
int main()
{
int x=1, y = 0;
try {
cout << mydiv(x,y) << endl;
} catch (myexception &iserr) {
cout << iserr.what() << endl;
}
return 0;
}
转换函数
类作为C++中的自定义类型,需要类型转换时,C++提供类型转换函数(type conversion function)将一个类型对象转换成另一类型的数据。
转换函数的实质就是运算符重载,只是重载的运算符不是内置的运算符而是类名这个特殊的自定义类型。
- 语法形式:
operator 类型名( ) [const]
{
实现转换的语句;
}
- 转换函数的基本规则:
转换函数只能是【成员函数】,【无返回类型】,【空参数】。
不能定义到void
的转换,也不允许转换成数组
或者函数类型
。
转换常定义为const
形式,原因是它并【不改变数据成员的值】。
explicit
关键字
在
C++
中,explicit
关键字用来【修饰类的构造函数】,被修饰的构造函数的类,不能发生相应的隐式类型转换
;
给【单参数的构造函数】使用explicit
关键字,阻止可能产生的隐式转换:由成员变量类型转换为类类型。
//explicit
#include <iostream>
using namespace std;
class Demo{
public:
//构造函数是单参数且没有默认值的情况下,有可能会发生隐式类型转换,explicit关键字作用:【阻止隐式类型转换】
explicit Demo(int val)
{
myval = val;
TRACE()
}
Demo(const Demo &obj) //拷贝构造
{
TRACE()
this->myval = obj.myval;
}
virtual ~Demo() { TRACE() }
public:
#if 0
Demo &operator = (Demo &obj) //重载赋值运算符 =
{
TRACE()
this->myval = obj.myval;
return *this;
}
#endif
int getval() const
{
return myval;
}
private:
int myval;
};
int main()
{
//Demo obj(123);
Demo obj(123);
obj = 321; //隐式类型转换,int --> Demo,转换过程: 321 --> Demo tmp(321) --> obj = tmp;
cout << obj.getval() << endl;
return 0;
}
//explicit
#include <iostream>
using namespace std;
class Demo{
public:
explicit Demo(int val)//explicit阻止隐式类型转换
{
TRACE()
myval = val;
cout << this->myval << endl;
}
virtual ~Demo() { TRACE() }
public:
int getval() const
{
return myval;
}
private:
int myval;
};
int main()
{
Demo obj(0);
obj = 321;//隐式类型转换 Demo tmp(321) --> obj = tmp;加关键字 explicit 则报错
obj = (Demo) 321;//explicit 阻止不了显式类型转换
cout << obj.getval() << endl;
return 0;
}
- 注意
编译时加:-std=c++0x
C++标准转换函数
编译时转换:reinterpret_cast、const_cast、static_cast
运行时候转换:dynamic_cast
-
reinterpret_cast
reinterpret_cast <new type>(expression)
将一个类型的指针转换为另一个类型的指针,它也允许从一个指针转换为整数类型//reinterpret_cast #include <iostream> using namespace std; int main() { int *p = NULL; char *q = NULL; //p = q; //error 不能将 char * 转 int * p = reinterpret_cast<int *> (q); //转换函数实现 char * --> int * 的转换 return 0; } reinterpret_cast #include <iostream> using namespace std; int main() { int *p = NULL; char *q = NULL; // reinterpret_cast: 实现不同类型指针之间的转换 int * --> char * q = reinterpret_cast <char *> (p); q = (char *) p; //强制类型转换 int * --> char *的转换 return 0; }
-
const_cast
const_cast <new type> (expression)
const
指针与普通指针间的相互转换,-
注意:不能将非常量指针变量转换为普通变量
//const_cast #include <iostream> using namespace std; int main() { const int *p = NULL; int *q = NULL; //q = p; //error 不能将 const int * 转 int * p = q; //right int * --> const int * q = const_cast <int *> (p); //转换函数实现 const int * --> int * 的转换 return 0; }
-
-
static_cast
(普通类类型转换)static_cast <new type> (expression)
主要用于【基本类型】间的相互转换,和具有【继承关系】间的类型转换//static_cast #include <iostream> using namespace std; class Base{ public: explicit Base() { } virtual ~Base() { } }; class Inherit : public Base{ public: explicit Inherit() { } ~Inherit() { } }; int main() { Base *p; Inherit *q; p = q; //right Inherit * --> Base *隐式类型转换 //q = p; //error 不允许由 Base * --> Inherit * 转换 q = static_cast<Inherit *> (p); //转换函数实现 Base * --> Inherit * 的转换 return 0; }
-
dynamic_cast
(有虚函数的类型转换)dynamic_cast<newtype>(expression)
只有类中含有虚函数才能用dynamic_cast;
仅能在继承类对象间转换
dynamic_cast
具有类型检查的功能,比static_cast
更安全
//dynamic_cast
#include <iostream>
using namespace std;
//基类
class Base{
public:
explicit Base()
{
}
virtual ~Base()
{
}
};
//子类
class Inherit : public Base{
public:
explicit Inherit()
{
}
~Inherit()
{
}
};
int main()
{
Base *p = NULL; //基类对象指针
Inherit *q = NULL; //子类对象指针
//p = q; //Inherit * --> Base *隐式类型转换
//q = p; //error 不允许由 Base * --> Inherit * 转换
//dynamic_cast函数:在运行时转换,实现基类和子类之间的类型转换,转换函数实现 Base * --> Inherit * 的转换
q = dynamic_cast<Inherit *> (p);
return 0;
}
//mycast
#include <iostream>
using namespace std;
class Demo{
public:
explicit Demo(int val) : myval(val)
{
TRACE()
}
virtual ~Demo()
{
TRACE()
}
public: //成员
operator int () //转换函数,必须是类的成员函数Demo --> int的转换
{
TRACE()
return this->myval;
}
private:
int myval;
};
int main()
{
Demo obj(123);
int i = 321;
i = obj; //等价:i = obj.operator int();实现类型转换:Demo --> int
cout << "i = " << i << endl;
return 0;
}
#include <iostream>
using namespace std;
class Base{
public:
Base(int val) : myval(val)
{
TRACE()
}
~Base()
{
TRACE()
}
private:
int myval;
};
class Demo{
public:
Demo(int val) : myval(val)
{
TRACE()
}
virtual ~Demo()
{
TRACE()
}
public: //成员
#if 1
operator Base () //转换函数,必须是类的成员函数 Demo --> Base的转换
{
TRACE()
return this->myval;
}
#endif
private:
int myval;
};
int main()
{
Demo obj1(123);
Base obj2(321);
obj2 = obj1;
// 等价:obj2 = obj1.operator Base();即 obj2 = 123; 过程:Base tmp(123) --> obj2 = tmp; 实现类型转换:Demo --> Base
return 0;
}
智能指针(本质模板类)
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
};
void nofreemem()
{
TRACE();
Demo *p = new Demo;
}
int main(int argc, char *argv[])
{
nofreemem();
nofreemem();
nofreemem();
return 0;
}
在该示例中,对象指针是函数内的局部变量,每次new出对象,但是nofreemem()返回时并没有delete,造成的后果就是对象指针指向的堆区在nofreemem()运行完成之后没有释放,且对象也没有销毁。
-
概念
智能指针(smart pointer)是个【特殊的类模板】,重载了
“->”
和“*”
运算符,实现了C++的【自动内存回收机制】。 -
实现原理
智能指针通用实现技术是使用【引用计数】(reference count)。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。 -
C++11中有四种智能指针(头文件
#include <memory>
)auto_ptr,shared-ptr,unique_ptr和weak-ptr。
其中auto_ptr有很多不足之处,在C++11
中已经建议废弃使用。都是在memory
头文件中声明的。
shared_ptr
shared_ptr
(共享资源的智能指针)被用来表示【共享的拥有权】。也就是说,当两段代码都需要访问一些数据,而它们又都没有独占该数据的所有权(从某种意义上来说就是该段代码负责销毁该对象)。
shared_ptr
是一种【计数指针】。当【引用计数】变为0时,shared_ptr
所【指向的对象】就会被删除。在给shared_ptr
【分配内存】时建议使用make_shared函数
,这样最安全。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
void safeheap()
{
#if 1
shared_ptr<Demo>p = make_shared<Demo>(Demo());
#else
shared_ptr<Demo>p(new Demo);
#endif
shared_ptr <Demo> pp(p);
cout << p << ":" << pp << endl;
p->func();
pp->func();
}
int main(int argc,char *argv[])
{
safeheap();
return 0;
}
实现原理:
tempalte <class T>
class shared_ptr {
public:
shared_ptr(T *str)
{
ptr = str;
}
shared_ptr(const T &obj)
{
ptr = obj.ptr;
}
~shared_ptr()
{
delete ptr;
}
private:
T *ptr;
};
#include <iostream>
#include <memory>
using namespace std;
class Demo {
public:
Demo()
{
TRACE()
}
~Demo()
{
TRACE()
}
public:
void prnmsg() const
{
TRACE()
}
};
int main()
{
shared_ptr<Demo> p(new Demo); //智能指针p指向Demo对象
shared_ptr<Demo> pp(p); //智能指针pp和p指针同时指向Demo对象
p->prnmsg();
pp->prnmsg();
(*p).prnmsg();
(*pp).prnmsg();
return 0;
}
unique_ptr
【同一时刻】【只能有一个】unique_ptr
指向给定对象
(通过禁止拷贝语义、只有移动语义来实现)。
unique_ptr
指针的声明周期从创建开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符
,用户可指定其他操作)。
unique_ptr
的使用包括:
为【动态申请的内存】提供异常安全;
将【动态申请的内存的所有权】传递给【某个函数】;
从某个函数返回【动态申请内存的所有权】;
在
容器
中保存指针;
所有unique_ptr
应该已具有具体的(但是无法在C++98中实现的)功能。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
void safeheap()
{
unique_ptr <Demo> p(new Demo);
p->func();
}
int main(int argc,char *argv[])
{
safeheap();
return 0;
}
weak_ptr
弱指针(weak_ptr)
,指向一个已经用shared_ptr
进行管理的对象;
只有当对象
存在的时候,才需要对其进行访问;
可能被其他人删除释放,且在最后一次使用之后调用其析构函数(通常用于释放那些不具名的内存(anon-memory)资源
weak_ptr可以保存一个“弱引用”,引用一个已经用shared_ptr管理的对象。为了访问这个对象,一个weak_ptr可以通过shared_ptr的构造函数或者是weak_ptr的成员函数lock()转化为一个shared_ptr。当最后一个指向这个对象的shared_ptr退出其声明周期并且这个对象被释放之后,将无法从指向这个对象的weak_ptr获得一个shared_ptr指针,shared_ptr的构造函数会抛出异常,而weak_ptr::lock也会返回一个空指针。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
int main(int argc,char *argv[])
{
shared_ptr <Demo> p = make_shared<Demo>();
weak_ptr <Demo> pp = p;
p.reset();
if (pp.expired()) {
cout << "Obj is not exist" << endl;
}
return 0;
}
STL
STL(Standard Template Library)即标准模板库,惠普实验室开发的一系列软件的统称。STL主要是一些“容器”的集合,这些“容器”有list、vector、set、map等等;STL也是算法和其他一些组件的集合,是世界上顶级C++程序员多年的杰作,是泛型编程的一个经典范例。
STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。STL现在是C++的一部分,内建在C++编译器中,因此不用额外安装什么。
STL可分为六个部分:
- 容器(containers)
特殊的数据结构,实现了数组、链表、队列等等,实质是模板类;
- 迭代器(interators)
一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载;
- 空间配置器(allocator)
容器的空间配置管理的模板类;
- 配接器(adapters)
用来修饰容器、仿函数、迭代器接口;
- 算法(algorithms)
读写容器对象的逻辑算法:排序、遍历、查找等等,实质是模板函数;
- 仿函数(functors)
类似函数,通过重载()运算符来模拟函数行为的类;
- 组件间关系
container(容器)通过allocator(配置器)取得数据存储空间,algorithm(算法)通过iterator(迭代器)存取container(容器)内容,functor(仿函数)可以协助algorithm(算法)完成不同的策略变化,adapter(配接器)可以修饰或套接functor(仿函数)。
标准容器简介
STL标准模板库是一种泛型编程(generic programming)。泛型编程关注的是算法,在C++中,利用模板完成编写独立于数据类型的代码。
STL容器包括:数组、链表、队列等等;能进行查找、排序、随机排队等等;
-
STL序列容器
-
vector
将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快捷,但是在中部或头部安插元素比较费时;
-
deque
是“double-ended queue”的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素非常快速,但是在中部或头部安插元素比较费时;
-
list
双向链表,不提供随机存取(按岁序走到需存取的元素),在任何位置上执行插入或删除动作都非常迅速,内部只需要调整一下指针。
-
-
STL关联容器
-
set/multiset
内部的元素根据其值自动排序,set内的相同数组的元素只能出现一次,multisets内可以包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找;
-
map、multimap
map的元素是成对的键值/实值,内部的元素根据其值自动排序,map内的相同数值的元素只能出现一次,Multimap内可包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找。
-
-
STL迭代器:iterator
其他一些容器:
hash_map,hash_set,hash_multiset,hash_multimap
标准容器的对比:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UYtdBKU-1629157866087)(https://i.loli.net/2021/08/03/8Vcsj5emuaLD7Pv.png)]
vector
vector向量相当于一个数组,在内存分配一块连续的内存空间进行存储。支持不指定vector大小的存储。通常此默认的内存分配能完成大部分情况下的存储。
优点:
可以不指定大小,使用push_back、pop_back来进行动态操作,随机访问方便,即支持[ ]操作符和vertor.at();节省空间。
缺点:
在内部进行插入删除操作效率低;
只能在vector的最后进行push和pop,不能在vector的头进行push和pop
当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放。
list
list双向列表
每一个结点都包括一个信息块Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
优点:
不使用连续内存完成动态操作
在内部方便的进行插入可删除操作
可在两端进行push、pop
缺点:
不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
相当于vector占用内存多
deque
deque双端队列
double-end queue,deque 是在功能上合并了vector和list
优点:
随机访问方便,即支持[]操作符和vector.at()
在内部方便的进行插入和删除操作
可在两端进行push\pop
缺点
占用内存多
使用区别
如果你需高效的随即存取,而不在乎插入和删除效率,使用vertor;
如果你需要大量的插入和删除,而不关心随机存取,则使用list
如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque.
智能指针通用实现技术是使用【引用计数】(reference count)。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
-
C++11中有四种智能指针(头文件
#include <memory>
)auto_ptr,shared-ptr,unique_ptr和weak-ptr。
其中auto_ptr有很多不足之处,在C++11
中已经建议废弃使用。都是在memory
头文件中声明的。
shared_ptr
shared_ptr
(共享资源的智能指针)被用来表示【共享的拥有权】。也就是说,当两段代码都需要访问一些数据,而它们又都没有独占该数据的所有权(从某种意义上来说就是该段代码负责销毁该对象)。
shared_ptr
是一种【计数指针】。当【引用计数】变为0时,shared_ptr
所【指向的对象】就会被删除。在给shared_ptr
【分配内存】时建议使用make_shared函数
,这样最安全。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
void safeheap()
{
#if 1
shared_ptr<Demo>p = make_shared<Demo>(Demo());
#else
shared_ptr<Demo>p(new Demo);
#endif
shared_ptr <Demo> pp(p);
cout << p << ":" << pp << endl;
p->func();
pp->func();
}
int main(int argc,char *argv[])
{
safeheap();
return 0;
}
实现原理:
tempalte <class T>
class shared_ptr {
public:
shared_ptr(T *str)
{
ptr = str;
}
shared_ptr(const T &obj)
{
ptr = obj.ptr;
}
~shared_ptr()
{
delete ptr;
}
private:
T *ptr;
};
#include <iostream>
#include <memory>
using namespace std;
class Demo {
public:
Demo()
{
TRACE()
}
~Demo()
{
TRACE()
}
public:
void prnmsg() const
{
TRACE()
}
};
int main()
{
shared_ptr<Demo> p(new Demo); //智能指针p指向Demo对象
shared_ptr<Demo> pp(p); //智能指针pp和p指针同时指向Demo对象
p->prnmsg();
pp->prnmsg();
(*p).prnmsg();
(*pp).prnmsg();
return 0;
}
unique_ptr
【同一时刻】【只能有一个】unique_ptr
指向给定对象
(通过禁止拷贝语义、只有移动语义来实现)。
unique_ptr
指针的声明周期从创建开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符
,用户可指定其他操作)。
unique_ptr
的使用包括:
为【动态申请的内存】提供异常安全;
将【动态申请的内存的所有权】传递给【某个函数】;
从某个函数返回【动态申请内存的所有权】;
在
容器
中保存指针;
所有unique_ptr
应该已具有具体的(但是无法在C++98中实现的)功能。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
void safeheap()
{
unique_ptr <Demo> p(new Demo);
p->func();
}
int main(int argc,char *argv[])
{
safeheap();
return 0;
}
weak_ptr
弱指针(weak_ptr)
,指向一个已经用shared_ptr
进行管理的对象;
只有当对象
存在的时候,才需要对其进行访问;
可能被其他人删除释放,且在最后一次使用之后调用其析构函数(通常用于释放那些不具名的内存(anon-memory)资源
weak_ptr可以保存一个“弱引用”,引用一个已经用shared_ptr管理的对象。为了访问这个对象,一个weak_ptr可以通过shared_ptr的构造函数或者是weak_ptr的成员函数lock()转化为一个shared_ptr。当最后一个指向这个对象的shared_ptr退出其声明周期并且这个对象被释放之后,将无法从指向这个对象的weak_ptr获得一个shared_ptr指针,shared_ptr的构造函数会抛出异常,而weak_ptr::lock也会返回一个空指针。
class Demo {
public:
explicit Demo() { TRACE(); }
virtual ~Demo() { TRACE(); }
void func() { TRACE(); }
};
int main(int argc,char *argv[])
{
shared_ptr <Demo> p = make_shared<Demo>();
weak_ptr <Demo> pp = p;
p.reset();
if (pp.expired()) {
cout << "Obj is not exist" << endl;
}
return 0;
}
STL
STL(Standard Template Library)即标准模板库,惠普实验室开发的一系列软件的统称。STL主要是一些“容器”的集合,这些“容器”有list、vector、set、map等等;STL也是算法和其他一些组件的集合,是世界上顶级C++程序员多年的杰作,是泛型编程的一个经典范例。
STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。STL现在是C++的一部分,内建在C++编译器中,因此不用额外安装什么。
STL可分为六个部分:
- 容器(containers)
特殊的数据结构,实现了数组、链表、队列等等,实质是模板类;
- 迭代器(interators)
一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载;
- 空间配置器(allocator)
容器的空间配置管理的模板类;
- 配接器(adapters)
用来修饰容器、仿函数、迭代器接口;
- 算法(algorithms)
读写容器对象的逻辑算法:排序、遍历、查找等等,实质是模板函数;
- 仿函数(functors)
类似函数,通过重载()运算符来模拟函数行为的类;
- 组件间关系
container(容器)通过allocator(配置器)取得数据存储空间,algorithm(算法)通过iterator(迭代器)存取container(容器)内容,functor(仿函数)可以协助algorithm(算法)完成不同的策略变化,adapter(配接器)可以修饰或套接functor(仿函数)。
标准容器简介
STL标准模板库是一种泛型编程(generic programming)。泛型编程关注的是算法,在C++中,利用模板完成编写独立于数据类型的代码。
STL容器包括:数组、链表、队列等等;能进行查找、排序、随机排队等等;
-
STL序列容器
-
vector
将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快捷,但是在中部或头部安插元素比较费时;
-
deque
是“double-ended queue”的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素非常快速,但是在中部或头部安插元素比较费时;
-
list
双向链表,不提供随机存取(按岁序走到需存取的元素),在任何位置上执行插入或删除动作都非常迅速,内部只需要调整一下指针。
-
-
STL关联容器
-
set/multiset
内部的元素根据其值自动排序,set内的相同数组的元素只能出现一次,multisets内可以包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找;
-
map、multimap
map的元素是成对的键值/实值,内部的元素根据其值自动排序,map内的相同数值的元素只能出现一次,Multimap内可包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找。
-
-
STL迭代器:iterator
其他一些容器:
hash_map,hash_set,hash_multiset,hash_multimap
标准容器的对比:
[外链图片转存中…(img-4UYtdBKU-1629157866087)]
vector
vector向量相当于一个数组,在内存分配一块连续的内存空间进行存储。支持不指定vector大小的存储。通常此默认的内存分配能完成大部分情况下的存储。
优点:
可以不指定大小,使用push_back、pop_back来进行动态操作,随机访问方便,即支持[ ]操作符和vertor.at();节省空间。
缺点:
在内部进行插入删除操作效率低;
只能在vector的最后进行push和pop,不能在vector的头进行push和pop
当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放。
list
list双向列表
每一个结点都包括一个信息块Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
优点:
不使用连续内存完成动态操作
在内部方便的进行插入可删除操作
可在两端进行push、pop
缺点:
不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
相当于vector占用内存多
deque
deque双端队列
double-end queue,deque 是在功能上合并了vector和list
优点:
随机访问方便,即支持[]操作符和vector.at()
在内部方便的进行插入和删除操作
可在两端进行push\pop
缺点
占用内存多
使用区别
如果你需高效的随即存取,而不在乎插入和删除效率,使用vertor;
如果你需要大量的插入和删除,而不关心随机存取,则使用list
如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque.