CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/09/24/C++学习笔记/#more
学习完了Java,继续C++的学习,C++还有点印象,之前用Qt的时候还写过。
1. C++基础知识
1.1 类的引入
在C语言中,使用struct
将多个数据组成一个新的数据结构,这一思维方式在C++里得到了扩展,变成了类class
的概念,既包含多个数据,也可包含函数。
- 示例:
{% codeblock lang:c [person1.c] %}
#include <stdio.h>
struct person {
char *name;
int age;
char *work;
void (*printf_info)(struct person *per); //函数指针
};
void printf_info(struct person *per)
{
printf(“name = %6s, age = %2d, work = %s\n”, per->name, per->age, per->work);
}
int main(int argc, char **argv)
{
struct person person[] = {
{“hceng”, 23, “Embedded engineer”, printf_info},
{“jack”, 21, “Graphic Designer”, printf_info}
};
person[0].printf_info(&person[0]);
person[1].printf_info(&person[1]);
return 0;
}
{% endcodeblock %}
{% codeblock lang:cpp [person2.cpp] %}
#include <stdio.h>
//struct person {
class person {
public:
char const name; //warning: ISO C++ forbids converting a string constant to ‘char’ [-Wwrite-strings]
int age;
char const *work;
void printf_info(void)
{
printf("name = %6s, age = %2d, work = %s\n", name, age, work); //函数实现直接写在了里面,且可访问数据
}
};
int main(int argc, char **argv)
{
struct person person[] = {
{“hceng”, 23, “Embedded engineer”},
{“jack”, 21, “Graphic Designer”}
};
person[0].printf_info();
person[1].printf_info();
return 0;
}
{% endcodeblock %}
编译:
gcc -o person1 person1.c
g++ -o person2 person2.cpp
- 结果:
name = hceng, age = 23, work = Embedded engineer
name = jack, age = 21, work = Graphic Designer
对该示例进行分析:
1.1 C语言里,使用
struct
将多个变量组合在了一起,由于C语言的struct
里不能有函数,因此使用函数指针来指向函数;
1.2main
里,使用struct person
这一新数据类型,定义了数组变量person[]
,并对每个成员初始化;
1.3 通过调用结构体里的函数指针,来调用函数,函数要访问结构体里的数据,因此还需要将结构体作为参数传入;
2.1 同样的功能,在C++里面,该示例做了如下改变:
2.2 因为扩充了strcut
的功能,使用新的关键字class
来表示集合;
2.3 加上了权限管理,比如这里的public
;
2.4 集合可以包含函数,同时也可以访问该集合的变量,无需传入参数;
2.5 初始化和调用,也精简了一点;
1.2 访问控制
C++通过public
、protected
、private
三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。
与Java不同的是C++中的public
、private
、protected
只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。
在类的内部(定义类的代码内部),无论成员被声明为public
、protected
还是private
,都是可以互相访问的,没有访问权限的限制。
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问public
属性的成员,不能访问private
、protected
属性的成员。
- 示例:
{% codeblock lang:cpp [person.cpp] %}
#include <stdio.h>
class Person {
private: //默认权限
char const *name;
int age;
char const *work;
public:
void setName(char const *name)
{
this->name = name;
}
int setAge(int age)
{
if(age < 0 || age > 150)
{
this->age = 0;
return -1;
}
this->age = age;
return 0;
}
void setWork(char const *work)
{
this->work = work;
}
void printInfo(void)
{
printf("name = %6s, age = %2d, work = %s\n", name, age, work); //函数实现直接写在了里面,且可访问数据
}
};
int main(int argc, char **argv)
{
int ret = 0;
Person per;
//per.name = “hceng”; //因为是private属性,无法直接访问
per.setName("hceng"); //通过类里的函数间接访问
ret = per.setAge(-23); //间接访问,可以实现对传入参数的控制
per.setWork("Embedded engineer");
per.printInfo();
return ret;
}
{% endcodeblock %}
- 结果:
name = hceng, age = 0, work = Embedded engineer
对该示例进行分析:
- 定义了一个
class
类Person
,有三个私有的成员变量,四个公共的成员函数;
2.1main
里,先创建了一个Person
类的对象per
;
2.2 通过对象per
不能直接访问私有属性,可以通过公有属性的函数进行间接访问;
2.3 将成员变量设置为私有,有利于对传入参数的控制;
1.3 程序结构
为了让代码结构更有层次感,类似C语言那种使用.h
头文件和.c
文件分离的方式。
示例程序结构如下:
.
├── dog.cpp
├── dog.h
├── main.cpp
├── Makefile
├── person.cpp
└── person.h
person.h
和dog.h
里面是类的定义;
person.cpp
和dog.cpp
里面是类的实现;
main.cpp
里面是函数的调用,Makefile
里面是整个工程代码的组织编译、清理;
文件比较多,就不贴代码了,示例代码见文末的Github链接。
在person.cpp
和dog.cpp
都定义了同名的printVersion()
函数,为了区分这两个函数,加入了命名空间namespace
这一关键词,将代码放在不同名字的命名空间里,调用的时候就有了区分。
调用也有三种方式:
1.直接使用,比如创建Person
对象:P::Person per;
;调用同名的函数:P::printVersion();
;
2.使用using
关键词声明,在主函数里或者外面,使用using D::Dog
声明,后面可以使用Dog
来表示D:Dog
;
3.使用using namespace D;
,将整个命名空间导入,后面可以使用Dog
来表示D:Dog
;
1.4 重载
与Java类似,如果函数名相同,函数的参数不同(类型、数量、顺序任一不同),同名不同参数的函数之间,互相称之为重载函数。调用时,根据传入的参数,自动选择对应的函数。
- 示例:
{% codeblock lang:cpp [main.cpp] %}
#include
using namespace std;
int add(int a, int b)
{
cout<<"add int+int = "<<a+b<<endl;
return a+b;
}
double add(int a, double b)
{
cout<<"add int+double = "<<a+b<<endl;
return (double)a+b;
}
double add(double a, int b)
{
cout<<"add double+int = "<<a+b<<endl;
return (double)a+b;
}
int add(int a, int b, int c)
{
cout<<"add int+int+int = "<<a+b+c<<endl;
return a+b+c;
}
int main(int argc, char **argv)
{
add(1, 1);
add(1, 1.5);
add(1.5, 1);
add(1, 1, 1);
return 0;
}
{% endcodeblock %}
- 结果:
add int+int = 2
add int+double = 2.5
add double+int = 2.5
add int+int+int = 3
对该示例进行分析:
- 四个同名函数,第一个和第二个参数类型不同,第二个和第三个参数类型位置不同,第一个和第三个参数数量不同,传入不同类型的参数,调用对应的函数;
1.5 指针与引用
C++和C语言中的指针是一样,可以通过指针来实现修改传入函数的参数。
在C++里新引入了引用的概念,就是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价。
语法如下:
类型 &引用名=目标变量名
int &e = d; //使用e代替d
C语言中&
是取址,C++中表示引用,C语言中没有引用。
- 示例:
{% codeblock lang:cpp [main.cpp] %}
#include
using namespace std;
void add1(int a)
{
a = a + 1;
}
void add2(int *b)
{
*b = *b + 1; //指针操作
}
void add3(int &c) //引用
{
c = c + 1;
}
int main(int argc, char **argv)
{
int a = 99;
int b = 99;
int c = 99;
int d = 99;
int &e = d; //使用e代替d
add1(a);
cout<<"a = "<<a<<endl;
add2(&b);
cout<<"b = "<<b<<endl;
add3(c);
cout<<"c = "<<c<<endl;
e++;
cout<<"d = "<<d<<endl;
return 0;
}
{% endcodeblock %}
- 结果:
a = 99
b = 100
c = 100
d = 100
对该示例进行分析:
1.1 函数
add1()
传入形参,在函数里面修改值,并不改变实参;
1.2 函数add2()
传入的是指针,在函数里修改该指针,改变了实参;
1.3 函数add3()
传入的是引用,在函数里修改该引用,改变了实参;
1.4 使用指针作为函数的形参达到的效果虽然和使用引用一样,但当调用函数时仍需要为形参指针变量在内存中分配空间,而引用则不需要这样,故在C++中推荐使用引用而非指针作为函数的参数;
2.1 使用e引用d,对e进行操作,实际就是对d进行操作,修改了d;
引用参考博客:C++:引用的简单理解
1.5 构造函数/析构函数/拷贝构造函数
构造函数:与Java里面的构造函数类似,用于实例化对象时,对其初始化;
析构函数:C++独有的,在对象销毁前瞬间,由系统自动调用析构函数;
拷贝构造函数:C++独有的,只有一个形参,且该形参是对本类类型对象的引用;
- 示例:
{% codeblock lang:cpp [Person.cpp] %}
#include
#include <unistd.h>
#include <string.h>
using namespace std;
class Person {
private:
char *name;
int age;
char *work;
public:
void setName(char *name)
{
this->name = name;
}
int setAge(int age)
{
if(age < 0 || age > 150)
{
this->age = 0;
return -1;
}
this->age = age;
return 0;
}
void setWork(char *work)
{
this->work = work;
}
void printInfo(void)
{
if (this->name && this->work)
cout<<"name = "<<name<<", age = "<<age<<", work = "<<work<<endl;
else
cout<<"name = NULL"<<", age = "<<age<<", work = NULL"<<endl;
}
//构造函数:和类名相同,无返回值,可以设置默认值
Person() //一但有了其它构造函数,默认的就必须写出来才能使用
{
this->name = NULL;
this->age = 0;
this->work = NULL;
}
Person(char *name, int age = 0, char *work = (char *)"none") //age和work设置有默认值
{
//this->name = name;
this->name = new char[strlen(name) + 1]; //为了测试析构函数的自动调用,这里使用开辟堆空间存放数据
strcpy(this->name, name);
//this->work = work;
this->work = new char[strlen(work) + 1];
strcpy(this->work, work);
if(age < 0 || age > 150)
this->age = 0;
else
this->age = age;
}
~Person()
{
cout << "~Person()"<<endl;
if (this->name) {
cout<<"name = "<<name<<endl;
delete this->name;
}
if (this->work) {
cout<<"work = "<<work<<endl;
delete this->work;
}
}
//拷贝构造函数
Person(Person &per)
{
cout << "Person(Person &per)"<<endl;
this->name = new char[strlen(per.name) + 1];
strcpy(this->name, per.name);
this->work = new char[strlen(per.work) + 1];
strcpy(this->work, per.work);
if(age < 0 || age > 150)
this->age = 0;
else
this->age = per.age;
}
};
void test_object()
{
Person per0; //调用无参构造函数(后面无括号,有括号相当于声明)
Person per1((char *)“jack”); //调用带参构造函数,未传入的未默认值
Person *per2 = new Person((char *)“hceng”, 23, (char *)“Embedded engineer”); //这种方式需要使用delete手动清除
per0.printInfo();
per1.printInfo();
per2->printInfo();
delete per2; //如果不手动删除,在调用完test_object()后,分配的堆空间不会被释放
cout << "delete per2 end"<<endl;
}
int main(int argc, char **argv)
{
test_object();
cout << “run test_object end”<<endl;
Person per1((char *)"hceng", 23, (char *)"Embedded engineer");
Person per2(per1);
per2.printInfo();
return 0;
}
{% endcodeblock %}
- 结果:
name = NULL, age = 0, work = NULL
name = jack, age = 0, work = none
name = hceng, age = 23, work = Embedded engineer
~Person()
name = hceng
work = Embedded engineer
delete per2 end
~Person()
name = jack
work = none
~Person()
run test_object end
Person(Person &per)
name = hceng, age = 23, work = Embedded engineer
~Person()
name = hceng
work = Embedded engineer
~Person()
name = hceng
work = Embedded engineer
对该示例进行分析:
1.1 定义了一个Person类,里面有三个私有的成员变量,四个公共的成员函数,两个构造函数,一个析构函数,一个拷贝构造函数;
1.2 外部两个函数,一个主函数,一个被主函数调用的test_object()
;2.1 主函数首先调用
test_object()
;
2.2test_object()
里使用三种方式实例化对象:调用无参构造函数,调用带参构造函数,new
创建一个对象,调用带参的构造函数;
2.3 带参的构造函数,参数可以指定默认值;
2.4 一但有了带参的构造函数,就必须写出无参的构造函数才能调用无参的构造函数;
2.5 前两种实例化方式,在对象销毁前瞬间,由系统自动调用析构函数;而第三种方式必须手动使用delete
删除;
3.1 将Person
类的实例化对象per1
作为参数传给Person per2()
,就是拷贝构造函数,将调用Person(Person &per)
;
3.2 在拷贝构造函数里,将传入的per
赋值给自己;
4.1 从打印结果分析,先调用test_object()
,实例化了三个对象,然后对应的打印出他们信息;
4.2 接着delete per2
,调用析构函数清理了per2
;
4.3 结束test_object()
的调用,释放per0
和per1
,自动调用析构函数清理;
4.4 接着实例化per1
,再将其作为参数传给per2
,此时调用拷贝构造函数,将per1
的内容拷贝给per2
;
4.5 结束时,自动调用析构函数,依次清理per0
和per2
;
1.6 静态成员
静态成员的提出是为了解决数据共享的问题。
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。
因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。
如下例子,需要统计总共生成了多少个类。
- 示例:
{% codeblock lang:cpp [person.cpp] %}
#include
#include <unistd.h>
#include <string.h>
using namespace std;
class Person {
private:
char *name;
int age;
char *work;
static int cnt;
public:
static int getCount1(void)
{
return cnt;
}
static int getCount2(void);
Person()
{
this->name = NULL;
this->age = 0;
this->work = NULL;
cnt++;
}
Person(char *name, int age = 0, char *work = (char *)"none")
{
//this->name = name;
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
//this->work = work;
this->work = new char[strlen(work) + 1];
strcpy(this->work, work);
if(age < 0 || age > 150)
this->age = 0;
else
this->age = age;
cnt++;
}
//析构函数
~Person()
{
}
//拷贝构造函数
Person(Person &per)
{
cnt++;
}
};
int Person::cnt = 0; //cnt定义和初始化
int Person::getCount2(void)
{
return cnt;
}
int main(int argc, char **argv)
{
Person per0;
Person per1((char *)“jack”);
Person *per2 = new Person((char *)“hceng”, 23, (char *)“Embedded engineer”);
Person per3(per1);
Person per4[10];
Person *per5 = new Person[10];
cout << "person number = "<<Person::getCount1()<<endl;
cout << "person number = "<<per1.getCount1()<<endl;
cout << "person number = "<<per5->getCount1()<<endl;
cout << "person number = "<<per5->getCount2()<<endl;
return 0;
}
{% endcodeblock %}
- 结果:
person number = 24
person number = 24
person number = 24
person number = 24
对该示例进行分析:
1.1 定义了一个
Person
类,包含四个成员变量,其中cnt
是静态成员变量;
1.2 接下来是两个静态方法,只有静态方法才能访问静态成员变量。两个静态方法前者实现了具体的内容,后者只是声明;
1.3 再是两个构造函数,在每个构造函数里添加cnt++
,实现每次实例化对象时调用计数;
1.4 最后是析构函数和拷贝构造函数,拷贝构造函数也会实例化对象,也要cnt++
;
2.1 前面的Person
类里,只是声明了cnt
,还需要定义和初始化;
2.2 函数getCount2()
也需要定义实现具体的内容;
3. 之后就可通过Person
或者任何其中一个对象访问cnt
;
1.7 友员函数
友元函数是可以直接访问类的私有成员的非成员函数。
它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend
。
比如下面的例子,add()
函数想访问Friend
类的私有成员。
- 示例:
{% codeblock lang:cpp [point.cpp] %}
#include
#include <unistd.h>
#include <string.h>
using namespace std;
class Point {
private:
int x;
int y;
public:
Point()
{
}
Point(int a, int b) : x(a+1), y(b)
{
}
int getX()
{
return x;
}
int getY()
{
return y;
}
void printInfo()
{
cout<<"sum: ("<<x<<", "<<y<<")"<<endl;
}
friend Point add(Point &p1, Point &p2);
};
Point add(Point &p1, Point &p2)
{
Point p;
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
int main(int argc, char **argv)
{
Point p1(1, 2);
cout<<“p1: (”<<p1.getX()<<", “<<p1.getY()<<”)"<<endl;
Point p2(2, 3);
cout<<"p2: ("<<p2.getX()<<", "<<p2.getY()<<")"<<endl;
Point sum = add(p1, p2);
sum.printInfo();
return 0;
}
{% endcodeblock %}
- 结果:
p1: (2, 2)
p2: (3, 3)
sum: (5, 5)
对该示例进行分析:
1 定义了类
Point
,包含两个私有成员变量,两个构造函数,三个成员函数,以及一个友员函数的声明;
2.1 第二个带参数的构造函数,有一个构造函数初始化列表;
2.2 它以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式;
2.3 在进行带参数的实例化对象时,成员x
就等于传入的参数a
加1;
3.1 在主函数里,先实例化p1
和p2
;
3.2 友员函数add()
可以直接访问f1
和f2
的私有成员变量,不再通过调用其成员函数进行访问,提高了效率;
1.8 运算符的重载
运算符的重载顾名思义就是对运算符进行重载,比如原来的加号+
,只能实现基本数据类型的相加,现在对其扩展功能,实现对类的相加。
在使用运算符重载前,必须先定义一个运算符重载函数,其名字为operator
,后随一个要重载的运算符。例如:
operator+ 加法
operator* 乘法
下例实现了对加法+0
,自增运算++
、打印输出<<
进行重载。
- 示例:
{% codeblock lang:cpp [point.cpp] %}
#include
#include <unistd.h>
#include <string.h>
using namespace std;
class Point {
private:
int x;
int y;
public:
Point()
{
}
Point(int a, int b) : x(a), y(b)
{
//x = a;
//y = b;
}
void printInfo()
{
cout<<"printInfo: ("<<x<<", "<<y<<")"<<endl;
}
friend Point operator+(Point &p1, Point &p2);
friend Point& operator++(Point &p);
friend Point operator++(Point &p, int a);
friend ostream& operator<<(ostream &o, Point p);
#if 0
//a+b
Point operator+(Point &p)
{
cout<<“operator+(Point &p)”<<endl;
Point r;
r.x = this->x + p.x;
r.y = this->y + p.y;
return r;
}
//++a
Point& operator++(void)
{
cout<<"operator++(void)"<<endl;
this->x += 1;
this->y += 1;
return *this;
}
//a++
Point operator++(int a)
{
cout<<"operator++(int a)"<<endl;
Point r;
r = *this;
this->x += 1;
this->y += 1;
return r;
}
#endif
};
//a+b
Point operator+(Point &p1, Point &p2)
{
cout<<“operator+(Point &p1, Point &p2)”<<endl;
Point r;
r.x = p1.x + p2.x;
r.y = p1.y + p2.y;
return r;
}
//++a
Point& operator++(Point &p)
{
cout<<“operator++(Point &p)”<<endl;
p.x += 1;
p.y += 1;
return p;
}
//a++
Point operator++(Point &p, int a)
{
cout<<“operator++(Point &p, int a)”<<endl;
Point r;
r = p;
p.x += 1;
p.y += 1;
return r;
}
//<<
ostream& operator<<(ostream &o, Point p) //第一个参数不是Point类型,不能写到成员函数里
{
cout<<"("<<p.x<<", “<<p.y<<”)";
return o;
}
int main(int argc, char **argv)
{
Point p1(1, 2);
Point p2(4, 5);
Point p;
p = p1 + p2;
p.printInfo();
p = ++p1;
p.printInfo();
p1.printInfo();
p = p2++;
p.printInfo();
p2.printInfo();
cout<<p<<p1<<p2<<endl;
return 0;
}
{% endcodeblock %}
- 结果:
operator+(Point &p1, Point &p2)
printInfo: (5, 7)
operator++(Point &p)
printInfo: (2, 3)
printInfo: (2, 3)
operator++(Point &p, int a)
printInfo: (4, 5)
printInfo: (5, 6)
(4, 5)(2, 3)(5, 6)
对该示例进行分析:
1.1 定义了类
Point
,包含两个私有变量,两个构造函数,一个成员函数,四个重载函数;
1.2 这四个重载函数,可以作为成员函数,在类里面实现,也可作为类外函数,在类外实现,只在类里面声明;
1.3 重载函数作为成员函数时,可以直接访问类的私有属性;重载函数作为类外函数时,通过友员的声明,访问类的私有属性;
1.4 同名的operator++()
,不含参数int a
表示前缀++
,带参数int a
的表示后缀++
;
1.5operator<<
因为第一个参数不是Point
类型,不能作为成员函数;
2.主函数里,使用重载后的+
可以实现Point
类的相加,以及Point
类的++
和<<
;
2. C++面向对象编程
如同Java面向对象编程一样,C++面向对象编程也有三大特性:封装性、继承性、多态性。
2.1 访问扩展和继承
这里定义一个基类Father
,两个继承于Father
的派生类Son
和Daughter
。
- 示例:
{% codeblock lang:cpp [person.cpp] %}
#include
#include <string.h>
#include <unistd.h>
using namespace std;
class Father {
private:
int money;
protected:
int room_key;
int room_car;
public:
int phone;
int address;
void working(void)
{
cout<<"father working"<<endl;
}
int getMoney(void)
{
return money;
}
void setMoeny(int money)
{
this->money = money;
}
};
class Son : public Father {
private:
int book;
using Father::phone;
public:
using Father::room_car;
//using Father::money; //无法改变父类private类型的属性
void learning(void)
{
cout<<"son learning"<<endl;
}
void getKey(void)
{
//s.money = 10; //private属性,子类也无法访问
room_key = 1; //protected属性,子类可以访问
}
};
class Daughter : protected Father {
private:
public:
void dancing(void)
{
cout<<“daughter dancing”<<endl;
room_key = 1; //不管何种继承,在派生类内部使用父类时无差别
}
void working(void) //覆写
{
cout<<"daughter working"<<endl;
}
};
int main(int argc, char **argv)
{
Son s;
Daughter d;
//s.money = 10; //private属性,外部无法访问
s.setMoeny(10);
//s.room_key = 1; //protected属性,外部无法访问
s.getKey();
s.working(); //public,外部可以直接访问
s.learning();
s.room_car = 1; //子类将其变为了public,使得可以访问
//s.phone = 1; //子类将其变为了private,使得不能访问
d.dancing(); //自己的属性没有
//d.working(); //protected继承,父类方法变为protected,外部无法访问
d.working();
return 0;
}
{% endcodeblock %}
- 结果:
father working
son learning
daughter dancing
daughter working
对该示例进行分析:
1.1 定义一个类
Father
,有一个私有属性money
,两个保护属性room_key
和room_car
,两个公共属性phone
和address
,以及三个共有的方法working()
、getMoney()
,setMoeny()
;
1.2 定义一个类Son
,公共继承于Father
。有一个私有属性book
,以及将父类的phone
属性变成了私有属性。将父类的保护属性room_car
变成了公共属性,父类的私有属性无法改变。有两个公有方法learning()
、getKey()
。其中getKey()
能访问了父类的保护属性,但不能访问私有属性;
1.3 定义一个类Daughter
,保护继承于Father
。有一个公共方法dancing()
,访问了父类的保护属性。覆写了父类的working()
方法;
2.1 主函数先实例化了Son
和Daughter
;
2.2 外部无法直接访问类中的私有属性money
,只能通过父类提供的公共函数setMoney()
进行访问;
2.3 外部无法直接访问父类的保护属性room_key
,只能通过父类提供的公共函数setKey()
进行访问;
2.4 函数working()
来自基类Father
,函数learning()
来自派生类Son
;
2.5room_car
在基类中原为保护属性,在派生类里被改为了公共属性,因此可被外界访问;
2.6 同理将原公共属性phone
改为了私有属性,外部无法访问;另外,派生类无法修改基类的私有属性;
2.7 类Daughter
,保护继承于Father
,父类的所有属性、函数,外部都无法直接访问;
2.8 派生类Daughter
覆写了基类的working()
,且为公共的,因此外部可以直接访问;
3.基类成员在派生类中的访问控制属性如下:
继承类型 \ 基类访问属性 | public | protected | private |
---|---|---|---|
public | public | public | 隔离 |
protected | protected | protected | 隔离 |
private | private | private | 隔离 |
2.2 多重继承
大多数程序使用单个基类的公用继承,但是在某些情况下,单继承是不够的,必须使用多继承。
比如,对于沙发床,既继承于沙发,也继承于床。
C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
因为不同的基类,可能有相同的属性,导致派生类出现二义性。
出现二义性,要么显示的指出要调用的是哪个类中的成员,要么就使用虚基类。
C++提供虚基类(virtual base class)的方法,使得在继承共同基类时只保留一份成员。
- 示例:
{% codeblock lang:cpp [person.cpp] %}
#include
#include <string.h>
#include <unistd.h>
using namespace std;
//method 1
class Sofa {
private:
int weight;
public:
void watchTV(void)
{
cout<<“Sofa: watch TV”<<endl;
}
void setWeight(int weight)
{
this->weight = weight;
}
int getWeight(void)
{
return weight;
}
};
class Bed {
private:
int weight;
public:
void sleep(void)
{
cout<<“Bed: sleep”<<endl;
}
void setWeight(int weight)
{
this->weight = weight;
}
int getWeight(void)
{
return weight;
}
};
class Sofabed : public Sofa , public Bed {
};
//method 2
class Furniture {
private:
int weight;
public:
void setWeight(int weight)
{
this->weight = weight;
}
int getWeight(void)
{
return weight;
}
};
class Sofa1 : virtual public Furniture {
public:
void watchTV(void)
{
cout<<“Sofa1: watch TV”<<endl;
}
};
class Bed1 : virtual public Furniture {
public:
void sleep(void)
{
cout<<“Bed1: sleep”<<endl;
}
};
class Sofabed1 : public Sofa1 , public Bed1 {
};
int main(int argc, char **argv)
{
Sofabed s;
Sofabed1 s1;
s.watchTV();
s.sleep();
//s.setWeight(100); //无法确定是哪一个基类的方法
s.Sofa::setWeight(100);
s1.watchTV();
s1.sleep();
s1.setWeight(100);
return 0;
}
{% endcodeblock %}
- 结果:
Sofa: watch TV
Bed: sleep
Sofa1: watch TV
Bed1: sleep
对该示例进行分析:
1.1 定义了一个类
Sofa
,包含一个私有属性weight
,三个公共的方法watchTV()
、setWeight()
、getWeight()
;
1.2 定义了一个类Bed
,包含一个私有属性weight
,三个公共的方法sleep()
、setWeight()
、getWeight()
;
1.3 定义了一个类Sofabed
,继承于Sofa
和Bed
;
2.1 定义了一个类Furniture
,包含一个私有属性weight
,两个公共的方法setWeight()
、getWeight()
;
2.2 定义了一个类Sofa1
,虚拟继承于Furniture
,有一个公共方法watchTV()
;
2.3 定义了一个类Bed1
,虚拟继承于Furniture
,有一个公共方法sleep()
;
2.4 定义了一个类Sofabed1
,继承于Sofa1
和Bed1
;
3.1 实例化了Sofabed
,可以直接访问各基类中的方法,但遇到了同名的方法,需要指定是哪一个类的方法;
3.2 实例化了Sofabed1
,可以直接访问各基类中的方法,因为其基类都继承于虚基类Furniture
,因此setWeight()
方法只存在一份,不会出现二义性;
2.3 构造顺序
我们构造的类开始越来越复杂,一个类可能含有一个或多个基类,基类有可能是虚拟基类,另外类本身也可能有对象成员,这里就理一下它们的构造顺序。
这里构造的类比较多,它们的关系如下:
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/180924/1.jpg)
其中虚线表示虚拟继承。
- 示例:
{% codeblock lang:cpp [CompanySofabed.cpp] %}
#include
#include <string.h>
#include <unistd.h>
using namespace std;
class Furniture {
public:
Furniture(void)
{
cout << “Furniture(void)” << endl;
}
};
class Verification {
public:
Verification(void)
{
cout << “Verification(void)” << endl;
}
};
class Sofa : virtual public Furniture, virtual public Verification {
public:
Sofa(void)
{
cout << “Sofa(void)” << endl;
}
};
class Bed : virtual public Furniture, virtual public Verification {
public:
Bed(void)
{
cout << “Bed(void)” << endl;
}
};
class Sofabed : public Sofa , public Bed {
public:
Sofabed(void)
{
cout << “Sofabed(void)” << endl;
}
Sofabed(char *s)
{
cout << "Sofabed(char *s)" << endl;
}
};
class Company {
public:
Company(void)
{
cout << “Company(void)” << endl;
}
Company(char *s)
{
cout << "Company(char *s)" << endl;
}
};
class Type {
public:
Type(void)
{
cout << “Type(void)” << endl;
}
};
class Date {
public:
Date(void)
{
cout << “Date(void)” << endl;
}
Date(char *s)
{
cout << "Date(char *s)" << endl;
}
};
class CompanySofabed : public Sofabed, virtual public Company {
private:
Type type;
Date date;
public:
CompanySofabed(void)
{
cout << “CompanySofabed(void)” << endl;
}
CompanySofabed(char *s1, char *s2, char *s3): Sofabed(s1), Company(s2), date(s3)
{
cout << "CompanySofabed(char *s)" << endl;
}
};
int main(int argc, char **argv)
{
CompanySofabed c((char *)“good”, (char *)“xxx”, (char *)“2018”);
return 0;
}
{% endcodeblock %}
- 结果:
Furniture(void)
Verification(void)
Company(char *s)
Sofa(void)
Bed(void)
Sofabed(char *s)
Type(void)
Date(char *s)
CompanySofabed(char *s)
对该示例进行分析:
1.1 基类与衍生类的关系如前面的图,这里就不复述了;
1.2 为每个类创建个构造函数,以便打印观察构造顺序;
2.1 从结果可以看出,虚拟基类最先执行构造,且构造一次;
2.2 然后再是非虚拟基类,依次按顺序构造;
2.3 再是类自己的对象成员,按声明的顺序构造;
2.4 最后是类自己构造;
3.1 注意对类自己对象成员的赋值,是使用的对象的名字;
3.2 析构顺序,与构造顺序相反;
2.4 多态
前面的封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。
而多态的目的则是为了接口重用。
也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
C++支持两种多态性:编译时多态性,运行时多态性;
a、编译时多态性(静态多态):通过重载函数实现;
b、运行时多态性(动态多态):通过虚函数实现;
利用重载前面已经介绍过了,就是传入的参数不一样,从而调用对应的同名函数。
这里主要分析动态多态,即利用虚函数实现。
对于虚函数,采用动态联编,对象里有一个指针,指向虚函数表,调用虚函数时,会根据对象里的指针找到表,从表中取出函数来执行
对于非虚函数,采用静态联编,编译时就确定调用哪个函数;
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/180924/2.jpg)
差别:静态联编效率高,动态联编支持多态;
- 示例:
{% codeblock lang:cpp [human.cpp] %}
#include
using namespace std;
class Human {
public:
virtual void eating(void)
{
cout << “use hand to eat” << endl;
}
virtual Human* test(void)
{
cout << "Human test" << endl;
}
virtual ~Human()
{
cout << "~Human" << endl;
}
};
class American : public Human {
public:
void eating(void)
{
cout << “use knife to eat” << endl;
}
virtual American* test(void)
{
cout << "American test" << endl;
}
virtual ~American()
{
cout << "~American" << endl;
}
};
class Chinese : public Human {
public:
void eating(void)
{
cout << “use chopsticks to eat” << endl;
}
virtual Chinese* test(void)
{
cout << "Chinese test" << endl;
}
virtual ~Chinese()
{
cout << "~Chinese" << endl;
}
};
void test_eating(Human& h)
{
h.eating();
}
void test_return(Human& h)
{
h.test();
}
int main(int argc, char **argv)
{
Human h;
American a;
Chinese c;
test_eating(h);
test_eating(a);
test_eating(c);
test_return(h);
test_return(a);
test_return(c);
return 0;
}
{% endcodeblock %}
- 结果:
use hand to eat
use knife to eat
use chopsticks to eat
Human test
American test
Chinese test
~Chinese
~Human
~American
~Human
~Human
对该示例进行分析:
1.1 定义了三个类,
Human
是基类,American
和Chinese
是其派生类;
1.2Human
里有一个virtual
修饰的eating()
虚函数,两个派生类同名的函数也自动是虚函数;
1.3 一般只有函数名、参数和返回值都相同的才能设为虚函数,但当返回值为当前对象的指针或引用时,也可以设为虚函数,这里每个类都有一个test()
函数,函数名、参数都一样,只是返回类型是对象的指针,因此可以被设置为虚函数;
1.4 析构函数一般都声明为虚函数,不然可能都释放的都是基类;
2.1 主函数里,分别实例化三个类,分别调用test_eating()
,因为eating()
是虚函数,编译时对象生成一个指针指向虚函数表,调用虚函数时,会根据对象里的指针找到表,从而找到对应的函数执行;
2.2test()
在每个类中的名字、参数相同,返回值为该对象的指针,因此可以设置为虚函数,从而实现调用各自类中对应函数;
3.1 只有类的成员函数才能声明为虚函数;
3.2 静态成员函数、内联函数、构造函数不能是虚函数;
3.3 对于重载(函数参数不同),也不可设为虚函数;
3.4 对于覆盖(函数参数、返回值相同),才可以设为虚函数;
3.5 函数参数相同,但是返回值是当前对象的指针或引用时,也可以设为虚函数;
2.5 类型转换
对比C语言,C++的类型转换也分显式转换和隐式转换。
在C语言中,隐式转换,类似将short
类型变量直接赋值给int
类型变量,就会自动隐式转换;
在C语言中,显式转换,通过(int)
修饰short
,就是指定转换类型的显示转换;
在C++中,隐式转换和C语言差不多,这里重点记录下C++的显式转换。
C++有四种强制类型转换操作符:static_cast
、dynamic_cast
、const_cast
、reinterpret_cast
。
格式为:强制类型转换操作符<type>(expression)
-
static_cast:最常用的类型转换符,比如基本数据类型之间的转换。
也可用于基类和子类之间指针或引用的转换,进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
注意:static_cast
不能转换掉const
、volital
e、或者__unaligned
属性。 -
dynamic_cast:用于运行时检查该转换是否类型安全,但只在多态场合时合法,即该类至少具有一个虚函数。
主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cas
t和static_cast
的效果是一样的;在进行下行转换时,dynamic_cast
具有类型检查的功能,比static_cast
更安全。
Type
必须是类的指针、类的引用或者void *
,且type
类型要和expression
一致。 -
**const_cast:**该操作符用于去除原来类型的
const
或volatile
属性,type
和expression
的类型是一样的。 -
reinterpret_cast:
reinterpret
的意思就是重新解释,此操作符的意思即为数据的二进制形式重新解释,但是不改变其值。
type
必须是一个指针、引用、算术类型、函数指针或者成员指针,它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。
如果type
是类指针类型,那么expression
也必须是一个指针;如果type
是一个引用,那么expression
也必须是一个引用。
和C语言的强制转换类似,没有安全性检查。
下面举个例子,对其具体分析。
- 示例:
{% codeblock lang:cpp [human.cpp] %}
#include
using namespace std;
class Human {
public:
virtual void eating(void)
{
cout << “use hand to eat” << endl;
}
};
class American : public Human {
public:
void eating(void)
{
cout << “use knife to eat” << endl;
}
};
class Chinese : public Human {
public:
void eating(void)
{
cout << “use chopsticks to eat” << endl;
}
};
class Chongqing : public Chinese {
public:
void eating(void)
{
cout << “use chopsticks to eat Chongqing” << endl;
}
};
void show_type(Human& h)
{
//Pointer
American *a;
Chinese *c;
Chongqing *cq;
if (a = dynamic_cast<American *>(&h))
cout << "this human is American" << endl;
if (c = dynamic_cast<Chinese *>(&h))
cout << "this human is Chinese" << endl;
if (cq = dynamic_cast<Chongqing *>(&h))
cout << "this human is Chongqing" << endl;
//Reference
if (cq = dynamic_cast<Chongqing *>(&h))
{
//American& am = dynamic_cast<American&>(h); //unable convert, generate aborted
Chinese& ch = dynamic_cast<Chinese&>(h);
Chongqing& cqr = dynamic_cast<Chongqing&>(h);
}
}
int main(int argc, char **argv)
{
double a = 4.44;
int b = a; //double to int
int c = (int)a;
int d = static_cast(a);
cout<<"b = “<<b<<” "<<"c = “<<c<<” "<<"d = "<<d<<endl;
const char *e = "www.hceng.cn";
char *f = const_cast<char *>(e); //clear const attribute
cout<<"e = "<<e<<" "<<"f = "<<f<<endl;
unsigned int *g =reinterpret_cast<unsigned int *>(f); //char * to unsigned int *
cout<<"g = "<<g<<endl;
Human hu;
American am;
Chinese ch;
Chongqing cq;
show_type(hu);
show_type(am);
show_type(ch);
show_type(cq);
American *h1 =static_cast<American *>(&hu);
//American *h2 = static_cast<American *>(&cq);
Chinese *h3 = static_cast<Chinese *>(&cq);
return 0;
}
{% endcodeblock %}
- 结果:
b = 4 c = 4 d = 4
e = www.hceng.cn f = www.hceng.cn
g = 0x55be4eac1347
this human is American
this human is Chinese
this human is Chinese
this human is Chongqing
对该示例进行分析:
1.1 定义了四个类,
Human
是American
和Chinese
的基类,Chinese
是Chongqing
的基类;
1.2 基类Human
有个虚函数eating()
,其它三个类都直接或间接从它继承过来,因此也各种有该虚函数;
2.1 主函数里,首先是将一个double
转换成int
,b
是通过隐式转换得到,c
是通过C语言风格的强制转换得到,d
是通过C++的static_cast
强制类型转换操作符得到;
2.2 字符串指针e
,定义时被const
修饰,通过const_cast
强制类型转换操作符,可以去掉const
得到f
,注意两者的数据类型要一致;
2.3 接着通过reinterpret_cast
强制类型转换操作符,将字符串指针char *
转换成了unsigned int *
类型的指针,从而直接打印g
就是f
的地址。另外因为前面去掉了const
,这里才能对f
操作;这里f
是指针,因此g
也必须是指针;
2.4 分别实例化四个类,调用show_type()
。在show_type()
里,先通过dynamic_cast
尝试类的平行转换,如果转换成功,说明传入的参数就是该类,两个无关联的类是不能直接转换的。在传入的是Chongqing
类时,即可向上转换成Chinese
,也可平行转换成Chongqing
,因此打印了两次。dynamic_cast
必须用于多态场合,因此各个类里面需要有虚函数;
2.5 在show_type()
里,前面dynamic_cast
的type
是指针,后面使用的type
是引用。使用引用的缺点是转换失败是产生异常,中断程序,因此最好使用type
是指针。
2.6 继续看主函数,使用static_cast
进行类的转换,将Human
向下转换成American
,static_cast
不会检查是否能转换,因此是不安全的;再将Chongqing
转换成American
,两个无关联的类之间转换,编译器检查到了就无法编译通过;最后尝试将Chongqing
向上转换成Chinese
,效果和dynamic_cast
是一样的;
3. C++高级编程
3.1 抽象类
前面介绍了虚函数,这里再衍生出纯虚函数,实现抽象类。
- 纯虚函数:在声明虚函数时被“初始化”为0的函数,形式如:
virtual void eating(void) = 0;
; - 抽象类:凡是有纯虚函数的类,都是抽象类,抽象类不能实例化,其派生的子类需覆写所有纯虚函数才能实例化,否则子类还是抽象类;
引入抽象类的意义:
1.为了实现运行时多态性,需要在基类中定义虚函数;
2.在很多情况下,基类本身生成对象是不合情理的。
例如,动物作为基类可以派生出小猫、小狗等实例化,但对动物进行实例化就明显是不合理的。
将函数定义为虚函数,则编译器要求在派生类中必须予以重写以实现多态性。
同时含有纯虚拟函数的类称为抽象类,它不能生成对象,这样就很好地解决了上述两个问题。
本次示例将各个类的代码分离成独自的文件,更贴近实际工作。
- 示例:
{% codeblock lang:Makefile [Makefile] %}
Human: main.o libHuman.so
g++ -o $@ $< -L. -lHuman
%.o : %.cpp
g++ -fPIC -c -o $@ $<
libHuman.so : Chinese.o American.o Human.o
g++ -shared -o $@ $^
clean:
rm -f *.o *.so Human
{% endcodeblock %}
分析:
- 这里使用了分层的思想,将提供类(各个类的声明和定义)和使用类(主函数里使用类)分开;
libHuman.so
包含了所有类,当对类做了修改时,只需要make libHuman.so
即可;- 之后
libHuman.so
和main.o
合并,当在主函数对类的使用做了修改时,只需要make
,不会重新编译各个类;- 执行时候,需要先指定库的路径,即
LD_LIBRARY_PATH=. ./Human
;
{% codeblock lang:cpp [human.h] %}
#ifndef _HUMAN_H
#define _HUMAN_H
#include
#include <string.h>
using namespace std;
class Human {
private:
char *name;
public:
void setName(char *name);
char *getName(void);
virtual void eating(void) = 0; //纯虚函数,提供模板
virtual void wearing(void) = 0;
virtual void driving(void) = 0;
virtual ~Human();
};
Human& CreateAmerican(char *name, int age, char *addr);
Human& CreateChinese(char *name, int age, char *addr);
#endif
{% endcodeblock %}
{% codeblock lang:cpp [human.cpp] %}
#include “Human.h”
void Human::setName(char *name)
{
this->name = name;
}
char *Human::getName(void)
{
return this->name;
}
Human::~Human()
{
cout<<"~Human()"<<endl;
}
{% endcodeblock %}
分析:
- 定义类
Human
,其中有三个纯虚函数;- 在类外,声明了两个函数,用于创建两个类。
- 把两个函数声明放在这是因为,减少主函数包含类的头文件,这样主函数只包含
Human.h
,其它类做了修改时,不再编译主函数;
{% codeblock lang:cpp [American.h] %}
#ifndef _AMERICAN_H
#define _AMERICAN_H
#include
#include “Human.h”
using namespace std;
class American : public Human{
private:
char addr[100];
int age;
public:
void eating(void);
void wearing(void);
void driving(void);
American();
American(char *name, int age, char *addr);
virtual ~American();
};
#endif
{% endcodeblock %}
{% codeblock lang:cpp [American.cpp] %}
#include “American.h”
void American::eating(void)
{
cout << “Eating American food” << endl;
}
void American::wearing(void)
{
cout << “Wearing American clothes” << endl;
}
void American::driving(void)
{
cout << “Driving American car” << endl;
}
American::American()
{
}
American::American(char *name, int age, char *addr)
{
setName(name);
this->age = age;
memset(this->addr, 0, 100);
strcpy(this->addr, addr);
}
American::~American()
{
cout << “~American()” << endl;
}
Human& CreateAmerican(char *name, int age, char *addr)
{
return *(new American(name, age, addr));
}
{% endcodeblock %}
分析:
- 类
American
继承于抽象类Human
,必须实现抽象类的所有纯虚函数;- 函数
CreateAmerican()
实现American
类的创建;
{% codeblock lang:cpp [Chinese.h] %}
#ifndef _CHINESE_H
#define _CHINESE_H
#include “Human.h”
using namespace std;
class Chinese : public Human{
public:
void eating(void);
void wearing(void);
void driving(void);
virtual ~Chinese();
};
#endif
{% endcodeblock %}
{% codeblock lang:cpp [Chinese.cpp] %}
#include “Chinese.h”
void Chinese::eating(void)
{
cout << “Eating Chinese food” << endl;
}
void Chinese::wearing(void)
{
cout << “Wearing Chinese clothes” << endl;
}
void Chinese::driving(void)
{
cout << “Driving Chinese car” << endl;
}
Chinese::~Chinese()
{
cout << “~Chinese()” << endl;
}
Human& CreateChinese(char *name, int age, char *addr)
{
return *(new Chinese());
}
{% endcodeblock %}
分析:
- 类
Chinese
继承于抽象类Human
,必须实现抽象类的所有纯虚函数;- 函数
CreateChinese()
实现Chinese
类的创建;
{% codeblock lang:cpp [main.cpp] %}
#include “Human.h”
void test_eating(Human *h)
{
h->eating();
}
int main(int argc, char **argv)
{
Human& a = CreateAmerican((char *)“jk”, 22, (char *)“America”);
Human& c = CreateChinese((char *)“hceng”, 23, (char *)“Chine”);
Human* h[2] = {&a, &c};
int i;
for (i=0; i<2; i++)
test_eating(h[i]);
delete &a;
delete &c;
return 0;
}
{% endcodeblock %}
分析:
- 通过函数间接创建两个子类,利用虚函数的特性调用各自的函数;
- 删除各自实例化的类,前提是类的析构函数是
virtual
修饰的;
- 结果:
Eating American food
Eating Chinese food
~American()
~Human()
~Chinese()
~Human()
3.2 函数模板
C++是一门强类型语言,所以无法做到像动态语言(python javascript)那样子,编写一段通用的逻辑,可以把任意类型的变量传进去处理。
泛型编程弥补了这个缺点,通过把通用逻辑设计为模板,摆脱了数据类型的限制,提供了继承机制以外的另一种抽象机制,极大地提升了代码的可重用性。
模板又分为函数模板和类模板,这里先分析函数模板。
函数模板只是编译指令,一般写在头文件中。编译程序时,编译器根据函数的参数来“推导”模板的参数,然后生成具体的模板函数。
函数模板只支持两种隐式转换:const转换和数组/函数指针转换。
const转换:函数参数为非const引用/指针, 它可以隐式转换为const引用/指针;
数组/函数指针转换:数组可以隐式转换为“指向第1个元素的指针”(a[0]);参数为“函数的名字”时,它隐式转换为“函数指针”;
其他隐式转换都不支持,比如:算术转换、派生类对象向上转换等;
函数模板支持重载,注意函数的选择规则:
1.先列出候选函数,包括普通函数、参数推导成功的模板函数;
2.这些候选函数,根据“类型转换”来排序(其中模板函数只支持前面介绍的两种隐式转换);
3.如果某个候选函数的参数,和调用时传入的参数更匹配,则选择该候选函数;
4.如果这些候选函数参数匹配度相同,如果只要一个非模板函数,就选它;如果只有模板函数,就选“更特化”的模板函数;否则导致“二义性”;
举个例子,需要实现一个比较两个数大小的函数,
在原来C语言中,需要定义多个功能一样,但参数不一样,函数名也不一样的函数,然后根据不同的参数,调用对应的函数。
在现在的C++中,利用模板函数和模板函数的重载,可以极大的精简该过程。
- 示例:
{% codeblock lang:cpp [compare.cpp] %}
#include
using namespace std;
/*
int& compare_max1(int& a, int& b)
{
return (a < b) ? b : a;
}
double& compare_max2(double& a, double& b)
{
return (a < b) ? b : a;
}
*/
template
T& compare_max1(T& a, T& b)
{
cout<<PRETTY_FUNCTION<<endl;
return (a < b) ? b : a;
}
template
const T& compare_max2(const T& a, const T& b)
{
cout<<PRETTY_FUNCTION<<endl;
return (a < b) ? b : a;
}
template
T* compare_max3(T* a, T* b)
{
cout<<PRETTY_FUNCTION<<endl;
return (a < b) ? b : a;
}
template
void test_func(T f)
{
cout<<PRETTY_FUNCTION<<endl;
}
float f1(int a, int b)
{
}
/*******************************/
#if 1
int& overload_template(int& a, int& b)
{
cout<<"1 "<<PRETTY_FUNCTION<<endl;
}
#endif
#if 1
template
T& overload_template(T& a, T& b)
{
cout<<"2 "<<PRETTY_FUNCTION<<endl;
}
#endif
#if 0
template
T overload_template(T a, T b)
{
cout<<"3 "<<PRETTY_FUNCTION<<endl;
}
#endif
#if 1
template
const T& overload_template(const T& a, const T& b)
{
cout<<"4 "<<PRETTY_FUNCTION<<endl;
}
#endif
int main(int argc, char **argv)
{
int ia = 2, ib = 3;
float fa = 2, fb = 3;
compare_max1(ia,ib);
compare_max1(fa,fb);
cout<<"-------------------------"<<endl;
compare_max2(ia,ib);
cout<<"-------------------------"<<endl;
char ca[] = "ca";
char cb[] = "cb";
char cc[] = "cc123";
compare_max1(ca, cb);
//compare_max1(ca, cc);
compare_max3(ca, cc);
cout<<"-------------------------"<<endl;
test_func(f1);
test_func(*f1);
cout<<"-------------------------"<<endl;
overload_template(ia,ib);
int *pa = &ia;
int *pb = &ib;
overload_template(pa, pb);
return 0;
}
{% endcodeblock %}
- 结果:
T& compare_max1(T&, T&) [with T = int]
T& compare_max1(T&, T&) [with T = float]
-------------------------
const T& compare_max2(const T&, const T&) [with T = int]
-------------------------
T& compare_max1(T&, T&) [with T = char [3]]
T* compare_max3(T*, T*) [with T = char]
-------------------------
void test_func(T) [with T = float (*)(int, int)]
void test_func(T) [with T = float (*)(int, int)]
-------------------------
1 int& overload_template(int&, int&)
2 T& overload_template(T&, T&) [with T = int*]
对该示例进行分析:
1.1 首先定义了三个模板函数:
compare_max1()
(参数为引用)、compare_max2()
(参数为const修饰的引用)、compare_max3()
(参数为指针);
1.2 再定义了一个模板函数test_func()
,和一个普通函数f1()
;
1.3 又定义四个名字一样的函数overload_template()
,第一个是普通函数,第二个是参数为引用的模板函数,第三个是参数为普通的模板函数,第四个是参数为const
修饰的引用模板函数;
1.4 宏__PRETTY_FUNCTION__
可以打印带参数类型的函数,便于分析;
2.1 主函数里,首先定义了两组不同类型的数据,都可以调用compare_max1()
,从打印结果可以看到,函数的参数类型都转换为传入的参数类型;
2.2 再调用compare_max2()
,从打印结果可以看到参数类型自动转换成了const
类型,该隐式转换是支持的;
2.3 再定义了三个数据,cc
的长度和ca
、cb
不一样。使用compare_max1()
传入ca
和cb
,可以看到数据类型都是char [3]
.
2.4 而如果使用compare_max1()
传入ca
和cc
,它们一个数据类型为char [3]
,一个为char [6]
,和模板函数的定义(两个参数类型一样)不一致,无法编译通过。
2.5 使用compare_max3()
传入ca
和cc
却有可以,因为函数模板定义的是指针类型,传入数组可以隐式转换为“指向第1个元素的指针”(a[0]),它们的数据类型就是一致的了;
2.6 使用test_func()
分别传入函数名字f1
和函数指针*f1
,其效果是一样的,因为传入参数为“函数的名字”时,它隐式转换为“函数指针”;
2.7 向函数overload_template()
传入两个int
类型数据,最优调用的是参数吻合的非模板函数;
2.8 向函数overload_template()
传入两个指针类型数据,非模板函数的参数类型为int
肯定不用调用了,就去看模板函数,模板函数的2、3都是最优吻合的,存在“二义性”,需要屏蔽一个。模板函数4因为有const
修饰,优先级稍微靠后一点,除非前面的都屏蔽,才会调用他它;
3.模板函数重载后,注意函数的选择规则;
3.3 类模板
C++除了支持函数模板,还支持类模板(Class Template)。
函数模板中定义的参数类型可以用在函数声明和函数定义中,类模板中定义的参数类型可以用在类声明和类实现中。
类模板的目的同样是为了摆脱了数据类型的限制,提升了代码的可重用性
声明类模板的语法为:
template<typename 参数类型1, typename 参数类型2, … >
class 类名{
//TODO:
};
类型参数不能为空,多个类型参数用逗号隔开。
一但声明了类模板,就可以将参数类型用于类的成员函数和成员变量了。
换句话说,原来使用int
、float
、char
等内置类型的地方,都可以用类型参数来代替。
- 示例:
{% codeblock lang:cpp [car.cpp] %}
#include
using namespace std;
template
class Car {
private:
T t;
public:
void car_weight(const T &t);
void printInfo(void);
};
template
void Car::car_weight(const T &t)
{
this->t = t;
}
template
void Car::printInfo(void)
{
cout<<“Car weight is:”<<t<<endl;
}
#if 1
//定做(重载)
template<>
class Car<char *> {
public:
void car_weight(const char *t);
void printInfo(void);
};
void Car<char *>::car_weight(const char *t)
{
cout<<“Car weight is:”<<t<<endl;
}
void Car<char *>::printInfo(void)
{
cout<<“Car<char *>”<<endl;
}
#endif
int main(int argc, char **argv)
{
Car ic;
ic.car_weight(1000);
ic.printInfo();
Car<double> dc;
dc.car_weight(999.99);
dc.printInfo();
Car<char *> cc;
cc.car_weight((char *)"1000kg");
cc.printInfo();
return 0;
}
{% endcodeblock %}
- 结果:
Car weight is:1000
Car weight is:999.99
Car weight is:1000kg
Car<char *>
对该示例进行分析:
1.1 先定义了一个类模板
Car
,它的私有成员变量t
和公共函数car_weight()
的参数都用T
代替,表示一个任意的数据类型;
1.2 然后实现成员函数car_weight()
和printInfo()
,注意格式;
1.3 然后定做了一个类,类似重载,它的类名和前面的类模板一样,但数据类型是确定了的,成员函数的内容也可以重新定义。
1.4 在实例化的时候,如果传入的数据类型刚好是char *
就会优先调用这个类;
2.1 主函数分别示例化了三个不同数据类型的对象,int
和double
类型都会调用前面的类模板Car
的成员函数;
2.2char *
类型的则会调用类Car<char *>
的成员函数;
3.4 异常
与Java类似,C++也有异常机制,关键字也差不多。
异常是程序在执行期间产生的问题。
C++异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。
C++异常处理涉及到三个关键字:try
、catch
、throw
。
-
throw
: 当问题出现时,程序会抛出一个异常,这是通过使用throw
关键字来完成; -
try
:try
块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个catch
块; -
catch
: 在想要处理问题的地方,通过异常处理程序捕获异常,catch
关键字用于捕获异常; -
示例:
{% codeblock lang:cpp [exception.cpp] %}
#include
#include <stdlib.h>
using namespace std;
class MyException {
public:
virtual void printInfo(void)
{
cout<<“This is MyException”<<endl;
}
};
class MySubException : public MyException {
public:
void printInfo(void)
{
cout<<“This is MySubException”<<endl;
}
};
//A->B->C
//void C(int in) throw(int, double) //C++11中丢弃
//noexcept(false)可能抛出异常;noexcept(true)/noexcept不会抛出异常;
void C(int in) noexcept(false)
{
int i = 1;
double d = 1.1;
if (0 == in)
cout<<"No Exception"<<endl;
else if (1 == in)
throw i;
else if (2 == in)
throw d;
else if (3 == in)
throw MyException();
else if (4 == in)
throw MySubException();
}
void B(int i)
{
cout<<“run B start”<<endl;
C(i);
cout<<“run B end”<<endl;
}
void A(int i)
{
try{
B(i);
} catch (int j) {
cout<<“catch int style exception”<<endl;
} catch (MyException &e) {
e.printInfo();
}//catch (…) {
//cout<<“catch other style exception”<<endl;
//}
}
void my_terminate_func()
{
cout<<“run my_terminate_func”<<endl;
}
int main(int argc, char **argv)
{
int i ;
set_terminate(my_terminate_func);
if (argc != 2)
{
cout<<"Usage: "<<endl;
cout<<argv[0]<<" <0|1|2|3|4>"<<endl;
return -1;
}
i = strtoul(argv[1], NULL, 0);
A(i);
return 0;
}
{% endcodeblock %}
- 结果:
hceng@android:/work/c++_learn/18th_exception$ ./exception 0
run B start
No Exception
run B end
hceng@android:/work/c++_learn/18th_exception$ ./exception 1
run B start
catch int style exception
hceng@android:/work/c++_learn/18th_exception$ ./exception 2
run B start
run my_terminate_func
Aborted (core dumped)
hceng@android:/work/c++_learn/18th_exception$ ./exception 3
run B start
This is MyException
hceng@android:/work/c++_learn/18th_exception$ ./exception 4
run B start
This is MySubException
对该示例进行分析:
1.1 定义了一个基类
MyException
,再派生出它的一个子类MySubException
;
1.2 基类MyException
有一个被virtual
修饰的虚函数printInfo
,其子类的printInfo
也自动为虚函数;
2.1 定义了三个函数,函数A
调用B
、函数B
调用函数C
;
2.2 函数A
将调用函数B
放在了try
块中,后面使用多个catch
捕获不同的异常;
2.3catch
后面指定想要捕捉的异常类型,这里为int
数据类型和MyException
类;
2.4 使用catch (...)
可以表示捕获其它未指定的异常类型;
2.5 函数B
调用函数C
;
2.6 函数C
有一个noexcept
修饰符,它是C++11新提供的异常说明符,用于声明一个函数不会抛出异常。使用noexcept
设置为不抛出异常,能阻止异常的传播;这里使用noexcept(false)
表示可能抛出异常;
2.7 函数C
抛出的异常有int
、double
、MyException
和MySubException
;
3.1 定义了函数my_terminate_func()
;
3.2 使用set_terminate()
将前面自己定义的函数作为异常处理函数;
4.1 主函数根据传入的不同参数,是函数C
抛出对应的异常。
4.2 当参数为0
时,函数C
没有抛出异常;
4.3 当参数为1
时,函数C
抛出int
异常,被函数A
捕获处理;
4.4 当参数为2
时,函数C
抛出double
异常,没有被任何函数捕获,最后调用terminate()
处理;
4.5 当参数为3
时,函数C
抛出MyException
异常,被函数A
捕获处理;
4.6 当参数为4
时,函数C
抛出MySubException
异常,因为是虚函数的缘故,这里调用子类的打印函数;
3.5 智能指针
在C++中,可以直接操作内存,给编程增加了不少的灵活性。
但是灵活性是有代价的,程序员必须负责自己负责释放自己申请的内存,否则就会出现内存泄露。
智能指针就是为了解决这个问题而存在的。它和其他指针没有本质的区别,主要的目的就是为了解决悬挂指针、内存泄露的问题。
其原理就是使用智能指针类SmartPointer
,将一个计数器RefBase
与类指向的对象相关联,当对象还有引用的时候,就不执行释放内存的操作,当引用计数为0时,就执行内存释放操作,并且将指针重置为NULL。
这里先尝试自己实现一个智能指针,理解其实现原理,然后再使用Android自带的智能指针重新测试。
- 示例:
{% codeblock lang:cpp [smartpointer.cpp] %}
#include
#include <stdlib.h>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase() : count(0) {}
void incStrong() { count++; }
void decStrong() { count–; }
int getStrongCount() { return count; }
};
class Person : public RefBase {
public:
Person() { cout<<“Person()”<<endl; }
~Person() { cout<<"~Person()"<<endl; }
void printInfo(void) { cout<<“This is Person”<<endl; }
};
template
class SmartPointer {
private:
T *sp;
public:
SmartPointer() : sp(0) {}
SmartPointer(T *pt)
{
cout<<"SmartPointer(T *pt)"<<endl;
sp = pt;
sp->incStrong();
}
SmartPointer(const SmartPointer &pt)
{
cout<<"SmartPointer(T &pt)"<<endl;
sp = pt.sp;
sp->incStrong();
}
~SmartPointer()
{
cout<<"~SmartPointer()"<<endl;
if (sp)
{
sp->decStrong();
if (sp->getStrongCount() == 0)
{
delete sp;
sp = NULL;
}
}
}
T *operator->() { return sp; }
T& operator*() { return *sp; }
};
template
void test_func(SmartPointer &pt)
{
SmartPointer sp = pt;
cout<<“In test_func:”<getStrongCount()<<endl;
sp->printInfo(); //(*sp).printInfo();
}
int main(int argc, char **argv)
{
int i;
SmartPointer<Person> sp = new Person();
cout<<"Before call test_func:"<<sp->getStrongCount()<<endl;
for (i=0; i<2; i++)
{
test_func(sp);
cout<<"After call test_func:"<<sp->getStrongCount()<<endl;
}
return 0;
}
{% endcodeblock %}
- 结果:
Person()
SmartPointer(T *pt)
Before call test_func:1
SmartPointer(T &pt)
In test_func:2
This is Person
~SmartPointer()
After call test_func:1
SmartPointer(T &pt)
In test_func:2
This is Person
~SmartPointer()
After call test_func:1
~SmartPointer()
~Person()
对该示例进行分析:
1.1 定义了类
RefBase
用于引用计数,在构造的时候就赋初值为0,提供三个函数进行加、减、查询计数;
1.2 定义了类Person
用于测试,继承于RefBase
;
1.3 定义了类模板SmartPointer
用于指针管理,包含指针类成员、三个构造函数、一个析构函数、两个运算符重载函数;
1.4 创建了模板函数test_func()
用于测试;
2.1 主函数里,首先new Person()
,传给sp
会调用到SmartPointer(T *pt)
,此时count
引用加1;
2.2 然后调用test_func()
,会调用到SmartPointer(const SmartPointer &pt)
,传给sp
,count
引用再加1;
2.3sp
作为局部变量在使用完后,自动释放,调用析构函数~SmartPointer()
,count
减1;
2.4 主函数循环,再次调用test_func()
,重复2.2、2.3的步骤;
2.5 主函数执行完,释放主函数的sp
,再次调用~SmartPointer()
,count
变为0,将会执行delete sp;
,最终调用~Person()
;
3.以后创建指针类,不需要Person *p = new Person();
,而使用SmartPointer<Person> p = new Person();
,就不需要自己手动delete p;
了。
- 示例:
{% codeblock lang:cpp [androidsmartpointer.cpp] %}
#include
#include <stdlib.h>
#include “RefBase.h”
using namespace std;
using namespace android::RSC;
class Person : public LightRefBase {
public:
Person() { cout<<“Person()”<<endl; }
~Person() { cout<<"~Person()"<<endl; }
void printInfo(void) { cout<<“This is Person”<<endl; }
};
template
void test_func(sp &pt)
{
sp sp = pt;
cout<<“In test_func:”<getStrongCount()<<endl;
sp->printInfo(); //(*sp).printInfo();
}
int main(int argc, char **argv)
{
int i;
sp<Person> sp = new Person();
cout<<"Before call test_func:"<<sp->getStrongCount()<<endl;
for (i=0; i<2; i++)
{
test_func(sp);
cout<<"After call test_func:"<<sp->getStrongCount()<<endl;
}
return 0;
}
{% endcodeblock %}
- 结果:
Person()
Before call test_func:1
In test_func:2
This is Person
After call test_func:1
In test_func:2
This is Person
After call test_func:1
~Person()
对该示例进行分析:
1.1 使用Android源码中自带的引用计数类和智能指针类,也就是Android轻量级指针;
1.2 相比自己实现的智能指针,Android源码提供的轻量级指针对引用计数有了原子操作,计数时避免了被其它相关的线程打断;
1.3 效果和前面自己写的智能指针基本一致;
3.7 Android强/弱指针
Android中定义了两种智能指针类型:强指针sp(strong pointer)、弱指针(weak pointer)
强指针与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用者都放弃了对该对象的引用,则该对象将被自动销毁。
弱指针也指向一个对象,但是弱指针仅仅记录该对象的地址,不能通过弱指针来访问该对象,也就是说不能通过弱指针来调用对象的成员函数或访问对象的成员变量。
要想访问弱指针所指向的对象,需首先将弱指针升级为强指针(通过wp类所提供的promote()
方法)。
弱指针所指向的对象是有可能在其它地方被销毁的,如果对象已经被销毁,wp的promote()
方法将返回空指针,这样就能避免出现地址访问错的情况。
为什么引入弱指针呢?
如下例中的这种情况,father
和son
相互引用,会导致释放时两者都没法释放,造成内存泄露。
因此引入弱指针来解决这种情况。
- 示例:
{% codeblock lang:cpp [pointer.cpp] %}
#include
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <utils/RefBase.h>
#include <LightPointeRefBase.h>
using namespace std;
#define LIGHTPOINT 1
#if LIGHTPOINT
using namespace android::RSC;
class Person : public LightRefBase {
private:
sp father;
sp son;
public:
Person() { cout<<“Person()”<<endl; }
~Person() { cout<<"~Person()"<<endl; }
void printInfo(void) { cout<<“This is Person”<<endl; }
void setFather(sp &father) { this->father = father; }
void setSon(sp &son) { this->son = son; }
};
#else
using namespace android;
class Person : public RefBase {
private:
wp father;
wp son;
public:
Person() { cout<<“Person()”<<endl; }
~Person() { cout<<"~Person()"<<endl; }
void printInfo(void) { cout<<“This is Person”<<endl; }
void setFather(sp &father) { this->father = father; }
void setSon(sp &son) { this->son = son; }
};
#endif
void test_func(int mode)
{
if(mode)
{
sp father = new Person();
sp son = new Person();
father->setSon(son);
}
else
{
sp father = new Person();
sp son = new Person();
father->setSon(son);
son->setFather(father);
}
}
int main(int argc, char **argv)
{
int mode = atoi(argv[1]);
if (mode < 2)
test_func(mode);
else
{
#if LIGHTPOINT
#else
wp s1 = new Person();
//s1->printInfo(); //ERROR, “wp” no override “->”
//(s1).printInfo(); //ERROR, “wp” no override ""
sp<Person> s2 = s1.promote();
if (s2 != 0)
s2->printInfo();
#endif
}
return 0;
}
{% endcodeblock %}
- 结果:
//#define LIGHTPOINT 1
hceng@android:/work/c++_learn/20th_strongpointer_weekpointer$ ./pointer 0
Person()
Person()
hceng@android:/work/c++_learn/20th_strongpointer_weekpointer$ ./pointer 1
Person()
Person()
~Person()
~Person()
//#define LIGHTPOINT 0
hceng@android:/work/c++_learn/20th_strongpointer_weekpointer$ ./pointer 0
Person()
Person()
~Person()
~Person()
hceng@android:/work/c++_learn/20th_strongpointer_weekpointer$ ./pointer 1
Person()
Person()
~Person()
~Person()
hceng@android:/work/c++_learn/20th_strongpointer_weekpointer$ ./pointer 2
Person()
This is Person
~Person()
对该示例进行分析:
1.1 条件编译,假设
#define LIGHTPOINT 1
,此时使用强指针,主函数根据传入参数调用test_func()
;
1.2mode = 0
时,father
和son
相互引用,使用完后,并没有调用析构函数释放;
1.3mode = 1
时,father
和son
没有相互引用,使用完后,调用析构函数进行了释放;
2.1 条件编译,假设#define LIGHTPOINT 0
,此时使用弱指针,主函数根据传入参数调用test_func()
;
2.2 可以看到无论father
和son
有无相互引用,结果都调用析构函数正常释放了;
3.1 在使用弱指针时,实例化s1
,无法通过s1
访问Person
类成员函数,只能先通过promote()
转化,才能访问Person
类成员函数;
4. 设计模式
4.1 单例模式
在Android源码中有许多的设计模式,其中单例模式就是最常见的一个。
单例模式就是一个类只能被实例化一次,即只能有一个实例化的对象的类。
像Windows系统的任务管理器一样,你无论打开多少次,始终显示的一个窗口。
定义一个统一的全局变量可以确保对象随时可以被访问,但不能防止创建多个对象。
一个最好的办法就是让类自身负责创建和保存它的唯一实例,并保证不创建其他实例,它还提供了一个访问该实例的方法,这就是单例模式的应用场景。
- 示例:
{% codeblock lang:cpp [singleton.cpp] %}
#include
#include <pthread.h>
#include <unistd.h>
using namespace std;
#if 0
//hungry mode
class Singleton {
private:
static Singleton *gInstance;
private:
Singleton() { cout<<“Singleton()”<<endl; }
public:
static Singleton *getInstance() { return gInstance; }
void printInfo() { cout<<"This is Singleton"<<endl; }
};
Singleton *Singleton::gInstance = new Singleton;
#else
//Lazy mode
class Singleton {
private:
static Singleton *gInstance;
static pthread_mutex_t g_tMutex;
private:
Singleton() { cout<<“Singleton()”<<endl; }
public:
static Singleton *getInstance()
{
#if 0
if (NULL == gInstance)
{
pthread_mutex_lock(&g_tMutex);
if (NULL == gInstance)
gInstance = new Singleton;
pthread_mutex_unlock(&g_tMutex);
}
#else
pthread_mutex_lock(&g_tMutex);
static Singleton *gInstance;
pthread_mutex_unlock(&g_tMutex);
#endif
return gInstance;
}
void printInfo() { cout<<"This is Singleton"<<endl; }
};
Singleton *Singleton::gInstance = NULL;
pthread_mutex_t Singleton::g_tMutex = PTHREAD_MUTEX_INITIALIZER;
#endif
void *start_routine_thread1(void *arg)
{
cout<<“this is thread 1 ……”<<endl;
Singleton *s = Singleton::getInstance();
s->printInfo();
return NULL;
}
void *start_routine_thread2(void *arg)
{
cout<<“this is thread 2 ……”<<endl;
Singleton *s = Singleton::getInstance();
s->printInfo();
return NULL;
}
int main(int argc, char **argv)
{
Singleton *s1 = Singleton::getInstance();
s1->printInfo();
Singleton *s2 = Singleton::getInstance();
s2->printInfo();
//Singleton *s3 = new getInstance();
//Singleton s4;
pthread_t threadID1;
pthread_t threadID2;
pthread_create(&threadID1, NULL, start_routine_thread1, NULL);
pthread_create(&threadID2, NULL, start_routine_thread2, NULL);
sleep(2);
return 0;
}
{% endcodeblock %}
- 结果:
hceng@android:/work/c++_learn/21th_singleton$ g++ -o singleton singleton.cpp -lpthread
hceng@android:/work/c++_learn/21th_singleton$ ./singleton
This is Singleton
This is Singleton
this is thread 1 ……
This is Singleton
this is thread 2 ……
This is Singleton
对该示例进行分析:
1.1 示例中有两种实现方法:饿汉模式(
hungry mode
)和懒汉模式(Lazy mode
);
1.2 饿汉就是第一时间需要食物,即类在定义的时候就进行实例化Singleton *Singleton::gInstance = new Singleton;
;
1.3 懒汉就是不到万不得已,是不会要食物的,即类在第一次用到类实例的时候才会去实例化Singleton *Singleton::gInstance = NULL; Singleton *s = Singleton::getInstance();
;
1.4 在访问量较小时,采用懒汉实现,以牺牲时间换空间;在访问量比较大,访问的线程比较多时,采用饿汉会有更好的性能,以空间换时间;
2.1Singleton
类里,有一个private
、static
的类指针gInstance
,保存唯一的实例;
2.2 构造函数为private
或protect
防止被外部函数调用,进行实例化;
2.3 实例的动作由一个public
的类方法getInstance()
代劳,该方法返回类唯一的实例;
2.4 懒汉模式因为在使用getInstance()
实例化时,可能发生冲突,因此需要加锁控制;
3.1 从示例结果可以看到,无论是主函数实例化类,还是在其它线程实例化类,结果都没有创建新的实例化对象,达到了目的;
3.2 构造函数变成了私有函数,只向外提供了getInstance()
接口,防止了通过其它途径实例化对象;
4.2 桥接模式
Bridge
桥接模式将抽象(Abstraction
)与实现(Implementation
)分离,使得二者可以独立地变化。
软件设计中,如果只有一维在变化,那么用继承就可以圆满的解决问题,如果有超过一维的变化,就最好用桥接模式。
举例一个操作系统和电脑公司的例子。
- 示例:
{% codeblock lang:cpp [install1.cpp] %}
#include
using namespace std;
class OS {
public:
virtual void Install() = 0;
};
class LinuxOS : public OS {
public:
virtual void Install() { cout<<“Install Linux OS”<<endl; };
};
class WindowsOS : public OS {
public:
virtual void Install() { cout<<“Install Windows OS”<<endl; };
};
class Company {
public:
virtual void Use() = 0;
};
class Msi : public Company {
public:
virtual void Use() { cout<<"Msi computer "; };
};
class Dell : public Company {
public:
virtual void Use() { cout<<"Dell computer "; };
};
class MsiUseLinuxOS : public LinuxOS, public Msi {
public:
void InstallOS() { Use(); Install(); }
};
class DellUseWindowsOS : public WindowsOS, public Dell {
public:
void InstallOS() { Use(); Install(); }
};
class MsiUseWindowsOS : public WindowsOS, public Msi {
public:
void InstallOS() { Use(); Install(); }
};
class DellUseLinuxOS : public LinuxOS, public Dell {
public:
void InstallOS() { Use(); Install(); }
};
int main(int argc, char **argv)
{
MsiUseLinuxOS a;
a.InstallOS();
DellUseWindowsOS b;
b.InstallOS();
return 0;
}
{% endcodeblock %}
- 结果:
Msi computer Install Linux OS
Dell computer Install Windows OS
对该示例进行分析:
1.1 定义了类
OS
,有一个方法Install()
,生成两个派生类LinuxOS
和WindowsOS
;
1.2 定义了类Company
,有一个方法Use()
,生成两个派生类Msi
和Dell
;
1.3 不同的电脑公司可能装不同的操作系统,就可能生成四个新类MsiUseLinuxOS
、DellUseWindowsOS
、MsiUseWindowsOS
、DellUseLinuxOS
;
2.1 主函数实例化了安装操作系统的电脑;
2.2 可见生成的类数量很多,为两个维度的乘积,且某一方发生了增添,最后生成的类也要有很大的改动,关系如下图:
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/180924/3.jpg)
- 示例:
{% codeblock lang:cpp [install2.cpp] %}
#include
using namespace std;
class OS {
public:
virtual void Install() = 0;
};
class LinuxOS : public OS {
public:
virtual void Install() { cout<<“Install Linux OS”<<endl; };
};
class WindowsOS : public OS {
public:
virtual void Install() { cout<<“Install Windows OS”<<endl; };
};
class Company {
public:
virtual void Use() = 0;
virtual void InstallOS() = 0;
};
class Msi : public Company {
private:
OS *impl;
public:
Msi(OS *impl) { this->impl = impl; };
virtual void Use() { cout<<"Msi computer "; };
virtual void InstallOS() { Use(); impl->Install(); };
};
class Dell : public Company {
private:
OS *impl;
public:
Dell(OS *impl) { this->impl = impl; };
virtual void Use() { cout<<"Dell computer "; };
virtual void InstallOS() { Use(); impl->Install(); };
};
int main(int argc, char **argv)
{
OS *lin = new LinuxOS();
OS *win = new WindowsOS();
Company *a = new Msi(lin);
Company *b = new Dell(win);
a->InstallOS();
b->InstallOS();
return 0;
}
{% endcodeblock %}
- 结果:
Msi computer Install Linux OS
Dell computer Install Windows OS
对该示例进行分析:
1.1 定义了类
OS
,有一个方法Install()
,生成两个派生类LinuxOS
和WindowsOS
;
1.2 定义了类Company
,有一个方法Use()
,生成两个派生类Msi
和Dell
;
1.3Msi
和Dell
有一个InstallOS()
方法,它并不是直接实现的,而是调用传入的OS
类的Install()
方法,即将抽象和实现分开;
2.1 主函数实例化了安装操作系统的电脑;
2.2 可见并没有生成各个厂家安装操作系统的类,且某一方发生了增添,都对对方没有任何影响,关系如下图:
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/180924/4.jpg)
5. 其它
所有示例源码:
Github
参考资料:
韦东山第四期Android驱动_C++快速入门