类和对象 在面向对象程序设计中,程序模块是由类构成的。类是对逻辑上相关的函数与数据的封装,它是对问题的抽象描述。
类的定义 这里以时钟为例,时钟类的定义如下:
1 2 3 4 5 6 7 8 9 class Clock {public : void setrime (int newH; int newM; int news) ; void showrime () ;private : int hour, minute, second; };
这里,封装了时钟的数据和行为,分别称为 Clock 类的数据成员和函数成员。定义类的语法形式如下:
1 2 3 4 5 6 7 8 9 10 11 12 class 类名称 { public : 外部接口 protected : 保护型成员 private : 私有成员 };
类成员的访问控制 访问控制属性可以有以下三种:公有类型(public)、私有类型(private)和保护类型(protected)
public : 成员定义了类的外部接口。公有成员用 public 关键字声明,在类外只能访问类的公有成员。对于时钟类,从外部只能调用 setTime()和 showTime()这两个公有类型的函数成员来改变或者查看时间。
private :声明的就是类的 私有成员 ,如果私有成员紧接着类名称,则关键字 private 可以省略。私有成员只能被本类的成员函数访问 ,来自类外部的任何访问都是非法的。这样,私有成员就完全隐蔽在类中,保护了数据的安全性。时钟类中的 hour,minute,second 都是私有成员。
protected :保护类型成员 的性质可以被其子类获得,但是不能通过类外访问得到。
成员函数的实现 函数的原型声明要写在类体中,原型说明了函数的参数表和返回值类型,而函数的具体实现是写在类定义之外的 。与普通函数不同的是,实现成员函数时要指明类的名称,具体形式为:
1 2 3 返回值类型 类名::函数成员名(参数表){ 函数体 }
例如:
1 2 3 4 5 6 7 8 9 void Clock::setTime (int newH,int newM,int newS) { hour=newH; minute=newM; second=newS; }void Clock::showTime () { cout<<hour<<":" <<minute<<":" <<second<<endl; }
内联成员函数 函数的调用过程要消耗一些内存资源和运行时间来传递参数和返回值,要记录调用时的状态,以便保证调用完成后能够正确地返回并继续执行。如果有的函数成员需要被频繁调用,而且代码比较简单,这个函数也可以定义为内联函数(inline function)。和普通内联函数相同,内联成员函数的函数体也会在编译时被插入到每一个调用它的地方。这样做可以减少调用的开销,提高执行效率,但是却增加了编译后的代码长度。所以要在权衡利弊的基础上慎重选择,只有对相对简单的成员函数才可以声明为内联函数。
内联函数的声明有两种方式:隐式声明 和 显式声明 。
将函数体直接放在类体中,这种方法称之为隐式声明。比如,将时钟类的 showTime()函数声明为内联函数,可以写作:
1 2 3 4 5 6 7 8 9 10 11 class Clock { public : void setTime (int newH,int newM,int newS) ; void showTime () { cout<< hour << ":" << minute << ":" << second <<endl; } private : int hour,minute,second; };
为了保证类定义的简洁,可以采用关键字 inline 显式声明的方式。即在函数体实现时,在函数返回值类型前加上 inline,类定义时不加入 showTime 的函数体。请看下面的表达方式:
1 2 3 4 inline void Clock::showTime () { cout < <hour << ":" << minute << ":" << second << endl; }
构造函数和析构函数 在定义对象时,可以同时对其数据成员赋初值,在定义对象时进行的数据成员设置,称为对象的初始化,在特定的对象使用结束时,需要进行一些清理工作,这里的初始化和清理工作分别由两个成员函数来完成,分别为 构造函数 和 析构函数 。
构造函数 构造函数在对象被创建的时候将被自动调用。如果类中没有写构造函数,编译器会自动生成一个隐含的默认构造函数,该构造函数的参数列表和函数体皆为空。如果类中声明了构造函数(无论是否有参数),编译器便不会再为之生成隐含的构造函数。
现将 Clock 类修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Clock { public : Clock (int NewH,int NewM,int NewS); void SetTime (int NewH,int NewM,int NewS) ; void ShowTime () ; private : int Hour,Minute,Second; }; Clock::Clock (int NewH,int NewM,int NewS) { Hour=NewH; Minute=NewM; Second=NewS; }
下面我们来看一看建立对象时构造函数的作用:
1 2 3 4 5 6 7 8 int main () { Clock c (0 ,0 ,0 ) ; c.ShowTime (); c.SetTime (8 ,30 ,30 ); return 0 ; }
在建立对象 c 时,会隐含调用构造函数,将实参用作初始值。由于 Clock 类中定义了构造函数,所以编译系统就不会在为其生成默认构造函数了。而这里自定义的构造函数带有形参,所以建立对象就必须给出初始值,用来调用构造函数时的实参。
拷贝构造函数 拷贝构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类的对象的引用。其作用是使用一个已经存在的对象(由拷贝构造函数的参数指定),去初始化的一个新对象。
如果没有定义一个类的拷贝构造函数,系统会在必要时自动生成一个默认的拷贝构造函数(因为有一些操作是默认会调用拷贝构造函数),这个默认的拷贝构造函数的功能是,把初始化对象的每个数据成员的值都复制到新建立的对象中。
拷贝构造函数的例子:通过水平和垂直两个方向的坐标值 X 和 Y 来确定屏幕上的一个点。点(Point)类定义如下:
1 2 3 4 5 6 7 8 9 10 11 class Point { public : Point (int xx=0 ,yy=0 ){X=xx;Y=yy;} Point (Point&p); int GetX () {return X;} int GetY () {return Y;} private : int X,Y; };
类中声明了内联构造函数函数和拷贝构造函数。拷贝构造函数的实现如下:
1 2 3 4 5 6 Point::Point (Point &p) { X = p.X; Y = p.Y; cout << "拷贝构造函数被调用" << endl; }
普通拷贝构造函数是在对象创建时被调用,而拷贝构造函数在以下三种情况下都会被调用:
1、当用类的一个对象去初始化该类的另一个对象时。例如:
1 2 3 4 5 6 7 int main(){ Point a (1 ,2 ) ; Point b (a) ; Point c = a; cout << b.GetX () << end1; return 0 ; }
以上对 b 和 c 的初始化都能够调用复制构造函数,两种写法只是形式上有所不同,执行的操作完全相同。
2、如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。例如:
1 2 3 4 5 6 7 8 9 10 void f (Point p) { cout<<p.GetX ()<<endl; }int main () { Point a(1 ,2 ); f (a); return 0 ; }
只有把对象用值传递时,才会调用复制构造函数,如果传递引用,则不会调用复制构造函数。由于这一原因,传递比较大的对象时,传递引用会比传值的效率高很多。
3、如果函数的返回值是类的对象,函数执行完成返回调用者时。 例如:
1 2 3 4 5 6 7 8 9 10 Point g () { Point a (1 ,2 ) ; return a; }int main () { Point b; b = g (); return 0 ; }
为什么在这种情况下,返回函数值时,会调用复制构造函数呢?
a 其实是一个局部变量,在 g()执行完后,就会被释放。执行语句 return a; 时,实际上是调用复制构造函数将 a 的值复制到临时对象中。
构造函数初始化列表
只有构造函数才可以使用初始化列表,其他函数使用会报错:only constructors take member initializers
构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
构造函数的初始化列表使得代码更加简洁,请看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;class Student {private : char *m_name; int m_age; float m_score;public : Student (char *name, int age, float score); void show () ; }; Student::Student (char *name, int age, float score): m_name (name), m_age (age), m_score (score){ }void Student::show () { cout << m_name << "的年龄是" << m_age << ",成绩是" << m_score << show (); return 0 ; }
运行结果:
1 2 小明的年龄是 15,成绩是 92.5 李华的年龄是 16,成绩是 96
如本例所示,定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:,后面紧跟 m_name(name), m_age(age), m_score(score) 语句,这个语句的意思相当于函数体内部的 m_name = name; m_age = age; m_score = score;
语句,也是赋值的意思。
使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。
初始化列表可以用于全部成员变量,也可以只用于部分成员变量。下面的示例只对 m_name 使用初始化列表,其他成员变量还是一一赋值:
1 2 3 4 Student::Student (char *name, int age, float score): m_name (name){ m_age = age; m_score = score; }
注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。请看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;class CCar { cout<<m_a<<"," <<m_b< “封闭类” private : int price; CTyre tyre; CEngine engine; public : CCar (int p, int tr, int tw); }; CCar::CCar (int p, int tr, int w): price (p), tyre (tr, w) { }; int main () { CCar car (20000 ,17 ,225 ) ; return 0 ; }
如果 CCar 类不定义构造函数,则 CCar car;会编译错误
编译器不知道 car.tyre 如何初始化
car.engine 的初始化没有问题: 用默认构造函数
所有要明确告诉 “对象中的成员对象”如何初始化?
定义封闭类的构造函数时, 添加初始化列表:
1 2 3 4 类名:: 构造函数(参数表): 成员变量 1 (参数表), 成员变量 2 (参数表), … { … }
析构函数 析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作,例如释放分配的内存、关闭打开的文件等。析构函数是在对象的生存期即将结束的时刻被自动调用的。
析构函数没有参数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~ 符号。
前向引用声明 我们知道 C++ 的类应当是先定义,然后使用。但在处理相对复杂的问题、考虑类的组合时,很可能遇到俩个类相互引用的情况,这种情况称为循环依赖。
1 2 3 4 5 6 7 8 9 10 11 12 class A { public : void f (B b) ; };class B { public : void g (A a) ; };
这里类 A 的公有成员函数 f 的形参是类 B 的对象,同时类 B 的公有成员函数 g 也以类 A 的对象为形参。由于在使用一个类之前,必须首先定义该类,因此无论将哪一个类的定义放在前面,都会引起编译错误。结局这个问题的方法,就是使用前向引用声明。前向引用声明,是在引用未定义的类之前,将该类的名字告诉编译器,试编译器知道那是一个类名。这样,当程序中使用这个类名时,编译器就不会认为是错误,而类的完整定义可以在程序的其他地方。在上述程序加上下面的前向引用声明,问题就解决了。
1 2 3 4 5 6 7 8 9 10 11 12 13 class B ; class A { public : void f (B b) ; };class B { public : void g (A a) ; };
类的静态成员 静态成员是解决同一个类的不同对象之间数据和函数共享问题的。
静态数据成员
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,该 static 成员也只会有一个,某个对象修改了这个 static 成员,其他对象也会受到影响;
一般的用法 类名 :: 标识符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> using namespace std;class Student { public : Student (char *name, int age, float score); void print () ; private : char *m_name; int m_age; float m_score; static int m_total; };int Student::m_total = 0 ; Student::Student (char *name, int age, float score) : m_name (name), m_age (age), m_score (score) { m_total++; }void Student::print () { printf ("[%d] name: %s, age: %d, score: %g\n" , m_total, m_name, m_age, m_score); }int main () { Student s1 ((char *)"A" , 1 , 11 ) ; s1.print (); Student s2 ((char *)"B" , 2 , 22 ) ; s2.print (); Student s3 ((char *)"C" , 3 , 33 ) ; s3.print (); return 0 ; }
输出为:
1 2 3 [1] name: A, age: 1, score: 11 [2] name: B, age: 2, score: 22 [3] name: C, age: 3, score: 33
静态函数成员 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> using namespace std;class Student { public : Student (char * name, int age, float score); void print () ; public : static int get_cnt () ; static float get_sum () ; private : static int m_cnt; static float m_sum; private : char * m_name; int m_age; float m_score; };int Student::m_cnt = 0 ;float Student::m_sum = 0.0f ; Student::Student (char * name, int age, float score): m_name (name), m_age (age), m_score (score) { m_cnt++; m_sum += score; }void Student::print () { printf ("name: %s, age: %d, score: %g\n" , m_name, m_age, m_score); }int Student::get_cnt () { return m_cnt; }float Student::get_sum () { return m_sum; }int main () { Student s1 ((char *)"A" , 1 , 11 ) ; s1.print (); Student s2 ((char *)"B" , 2 , 22 ) ; s2.print (); Student s3 ((char *)"C" , 3 , 33 ) ; s3.print (); printf ("student_total: %d, score_total: %g\n" , Student::get_cnt (), Student::get_sum ()); return 0 ; }
输出为:
1 2 3 4 name: A, age: 1, score: 11 name: B, age: 2, score: 22 name: C, age: 3, score: 33 student_total: 3, score_total: 66
构造函数的调用规则 默认情况下,c++ 编译器至少给一个类添加 3 个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行拷贝
构造函数的调用规则如下:
如果用户定义了有参构造函数,c++ 不在提供默认无参构造函数,但是会提供默认拷贝构造
如果用户定义了拷贝构造函数,c++ 不在提供其他构造函数
深拷贝和浅拷贝 浅拷贝:简单的赋值拷贝操作 深拷贝:在堆区重新申请空间,进行拷贝操作;
类对象作为类成员 对象模型和 this 指针 成员变量和成员函数分开存储 空对象 c++ 编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置。每个空对象也应该有一个独一无二的内存地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> class Person { };void test01 () { Person p; std::cout << "size of p =" << sizeof (p) << std::endl; }int main () { test01 (); return 0 ; }
输出:
非静态成员变量 属于类对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> class Person { int a; };void test01 () { Person p; std::cout << "size of p =" << sizeof (p) << std::endl; }int main () { test01 (); return 0 ; }
输出:
静态成员变量 不属于类对象上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> class Person { static int a; };int Person::a = 10 ;void test01 () { Person p; std::cout << "size of p =" << sizeof (p) << std::endl; }int main () { test01 (); return 0 ; }
输出
非静态成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> class Person { void a () ; };void test01 () { Person p; std::cout << "size of p =" << sizeof (p) << std::endl; }int main () { test01 (); return 0 ; }
输出:
静态成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> class Person { static void a () ; };void Person::a () { }void test01 () { Person p; std::cout << "size of p =" << sizeof (p) << std::endl; }int main () { test01 (); return 0 ; } 输出: ```shell size of p = 1
this 指针
指向被调用的成员函数所属的对象
隐含每一个非静态成员函数内的一种指针
不需要定义,直接使用即可
用途
当形参和成员变量同名时,可用 this 指针来区分
在类的非静态成员函数中返回对象本身,可使用 return *this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> class Person { public : Person (int age) {this ->age = age;} Person& PersonAgeAdd (Person &p) { this ->age += p.age; return *this ; } int age; };void test01 () { Person p (1 ) ; }void test02 () { Person p1 (10 ) ; Person p2 (10 ) ; p2.PersonAgeAdd (p1).PersonAgeAdd (p1).PersonAgeAdd (p1); std::cout << "p2.age:" << p2.age << std::endl; }int main () { test01 (); test02 (); return 0 ; }
空指针访问成员函数 c++ 中空指针也是可以调用成员函数的,但是也要注意有没有用到 this 指针。如果用到 this 指针,需要加以判断保证代码的健壮性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> class Person { public : void showClassName () { std::cout << "hello, world!" << std::endl; } void showPersonAge () { std::cout << "age=" << age << std::endl; } int age; };void test01 () { Person *p = NULL ; p->showClassName (); p->showPersonAge (); }int main () { test01 (); return 0 ; }
输出:
1 2 3 4 5 hello, world! age= Process finished with exit code -1073741819 (0xC0000005)
当上面的测试用例在访问类的属性时,代码异常退出。可以取消注释代码,增强程序的健壮性。
const 修饰成员函数 常函数
成员函数后加 const 后我们称这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字 mutable 后,在常函数中依然可以修改
常对象
声明对象前加 const 称对象为常对象
常对象只能调用常函数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> class Person { public : void hello () { std::cout << "hello, world!" << std::endl; } void showAge () const { this ->m_B = 20 ; } int m_age; mutable int m_B; };void test01 () { const Person p; p.m_B = 10 ; p.hello (); p.showAge (); }int main () { test01 (); return 0 ; }
友元 在程序里面,有些私有属性也想让类外特殊的一些函数或者类进行访问
友元的关键词为 friend
友元的三种实现
全局函数做友元
类做友元
成员函数做友元
全局函数做友元 一个函数可以访问一个类中的私有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;class Building { friend void goodGay (Building *building) ; public : Building () { m_stittingRoom = "keting" ; m_BedRoom = "woshi" ; } string m_stittingRoom; private : string m_BedRoom; };void goodGay (Building *building) { cout << "now:" << building->m_BedRoom << endl; }void test01 () { Building building; goodGay (&building); }int main () { test01 (); return 0 ; }
输出:
类做友元 一个类可以访问另外一个类中的私有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <string> using namespace std;class Building ;class GoodGay {public : void visit () ; Building *building; GoodGay (); };class Building { friend class GoodGay ; public : Building (); string m_stittingRoom; private : string m_BedRoom; }; Building::Building () { m_stittingRoom = "keting" ; m_BedRoom = "woshi" ; } GoodGay::GoodGay () { building = new Building; }void GoodGay::visit () { cout << building->m_BedRoom << endl; }void test01 () { GoodGay goodGay; goodGay.visit (); }int main () { test01 (); return 0 ; }
输出:
成员函数做友元 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> #include <string> using namespace std;class Building ;class GoodGay { public : void visit () ; void visit1 () ; Building *building; GoodGay (); ~GoodGay (); };class Building { friend void GoodGay::visit1 () ; public : Building (); string m_stittingRoom; private : string m_BedRoom; }; Building::Building () { m_stittingRoom = "keting" ; m_BedRoom = "woshi" ; } GoodGay::GoodGay () { building = new Building; } GoodGay::~GoodGay () { if (building) { delete building; } };void GoodGay::visit () { cout << "visit:" << building->m_BedRoom << endl; }void GoodGay::visit1 () { cout << "visit:" << building->m_BedRoom << endl; }void test01 () { GoodGay goodGay; goodGay.visit (); goodGay.visit1 (); }int main () { test01 (); return 0 ; }
继承 通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为[派生类](https://baike.baidu.com/item / 派生类 / 9589520?fromModule=lemma_inlink),又称为子类。
class A: public/private/protected B;
A 称为子类或者派生类
B 称为父类或者基类
继承方式 继承的语法:class 子类: 继承方式 父类
公共继承
保护继承
私有继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <string> #include <map> using namespace std;class GoodGay { public : int a; protected : int b; private : int c; };class A : public GoodGay { public : void func () { a = 10 ; b = 20 ; } };class B : protected GoodGay { public : void func () { a = 10 ; b = 20 ; } };class C : private GoodGay { public : void func () { a = 10 ; b = 20 ; } };void test01 () { A A1; A1.a = 10 ; B B1; C C1; }int main () { test01 (); return 0 ; }
继承中的对象模型
在父类中所有非静态成员属性都有会被子类继承下去
父类中私有的成员属性是被编译器给隐藏了,因此访问不到,但是还是会被子类继承下去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> using namespace std;class Father { public : int a; protected : int b; private : int c; };class Son : public Father { public : int d; };void test01 () { std::cout << sizeof (Father) << std::endl; std::cout << sizeof (Son) << std::endl; }int main () { test01 (); return 0 ; }
输出:
继承中的构造和析构顺序
父类的构造函数
子类的构造函数
子类的析构函数
父类的析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;class Father { public : Father (); ~Father (); }; Father::Father () { cout << "Father::Father" << endl; } Father::~Father () { cout << "Father::~Father" << endl; }class Son : public Father { public : Son (); ~Son (); }; Son::Son () { cout << "Son::Son" << endl; } Son::~Son () { cout << "Son::~Son" << endl; }void test01 () { Son s1; }int main () { test01 (); return 0 ; }
输出:
1 2 3 4 5 6 7 Father::Father Son::Son Son::~Son Father::~Father
继承中同名成员处理方式
访问子类同名成员时,直接访问即可
访问父类同名成员时,需要加作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;class Father { public : void printf () { cout << "father::printf" << endl; } int m = 100 ; };class Son : public Father { public : void printf () { cout << "son::printf" << endl; } int m = 200 ; };void test01 () { Son s1; s1.printf (); s1.Father::printf (); cout << s1.m << endl; cout << s1.Father::m << endl; }int main () { test01 (); return 0 ; }
输出
1 2 3 4 son::printf father::printf 200 100
继承中同名静态成员处理方式 静态成员:
静态成员函数只能访问静态成员变量
静态成员属性:类内声明,类外初始化
访问方式:
通过对象访问
通过类名访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> using namespace std;class Father { public : static void printf () { cout << "father::printf" << endl; } static int m; };int Father::m = 100 ;class Son : public Father { public : static void printf () { cout << "son::printf" << endl; } static int m; };int Son::m = 200 ;void test01 () { Son s1; s1.printf (); s1.Father::printf (); Son::printf (); Son::Father::printf (); cout << s1.m << endl; cout << s1.Father::m << endl; cout << Son::m << endl; cout << Son::Father::m << endl; }int main () { test01 (); return 0 ; }
输出:
1 2 3 4 5 6 7 8 son::printf father::printf son::printf father::printf 200 100 200 100
虚函数和纯虚函数 首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class A { public : virtual void foo () { cout<<"A::foo() is called" <<endl; } };class B :public A { public : void foo () { cout<<"B::foo() is called" <<endl; } };int main (void ) { A *a = new B (); a->foo (); return 0 ; }
输出:
虽然 a 指向了 class A,但是调用的是 class B 中的 foo()
多继承语法 语法:class 子类:继承方式 父类 1, 继承方式 父类 2 …
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++ 开发中不建议使用多继承语法开发
菱形继承 概念
两个派生类继承同一个基类
又有某个类同时继承两个派生类
这种继承被称为菱形继承,或者钻石分类
菱形继承问题
羊继承了动物的数据,驼继承了动物的数据,当羊驼继承时,会产生二义性。
羊驼继承自动物的数据继承了两份
第一种问题可以使用作用域解决;
第二种问题可以使用虚继承解决;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> using namespace std;class Animal { public : int m_Age; };class Sheep : virtual public Animal { };class Tuo : virtual public Animal { };class SheepTuo : public Sheep, public Tuo { };void test01 () { SheepTuo t1; t1.Sheep::m_Age = 100 ; t1.Tuo::m_Age = 200 ; cout << t1.Sheep::m_Age << endl; cout << t1.Tuo::m_Age << endl; cout << t1.m_Age << endl; }int main () { test01 (); return 0 ; }
多态 关键字:virtual
概念 多态分为两类:
静态多态和动态多态的区别:
静态多态的函数地址早绑定:编译阶段时确定函数地址
动态多态的韩式地址晚绑定:运行阶段时确定函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std;class Animal { public : virtual void speak () { cout << "animal is speaking" << endl; } int m_Age; };class Cat : public Animal { public : void speak () { cout << "cat is speaking" << endl; } };class Dog : public Animal { public : void speak () { cout << "dog is speaking" << endl; } };void speak_func (Animal &animal) { animal.speak (); }void test01 () { Cat cat; Dog dog; speak_func (cat); speak_func (dog); }int main () { test01 (); return 0 ; }
输出:
1 2 cat is speaking dog is speaking
动态多态满足条件:
有继承关系
子类重写父类的虚函数
动态多态的使用:
父类的指针或者引用指向子类对象
多态带来的好处:
组织结构清晰
可读性强
重写:函数返回值类型,函数名,入参完全一致
基本原理 虚函数表
纯虚函数和抽象类 在多态中,通常父类中虚函数的实现是毫无意义的
纯虚函数语法:virtaul 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类
抽象类的特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;class Base { public : virtual void func () = 0 ; };class Son : public Base { public : void func () { cout << "son func" << endl; } };class Son2 : public Base { };void test01 () { Base *base = new Son; base->func (); delete base; }int main () { test01 (); return 0 ; }
虚析构和纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
纯虚析构:需要声明也需要实现。
解决方法:将父类中的析构函数改为虚析构或者纯虚析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 #include <iostream> #include <string> using namespace std;class Animal { public : virtual void speak () = 0 ; Animal () { cout << "Animal::Animal" << endl; } virtual ~Animal () { cout << "Animal::~Animal" << endl; } };class Sheep : public Animal { public : Sheep (std::string name); void speak () { std::string *hello = new std::string ("hello, world" ); cout << *name << "Sheep is speaking" << endl; } ~Sheep () { if (name) { delete name; name = nullptr ; } cout << "Sheep::~Sheep" << endl; } string *name = nullptr ; }; Sheep::Sheep (std::string name) { cout << "Sheep::Sheep" << endl; this ->name = new string (name); }void test01 () { Animal *sheep = new Sheep ("Tom" ); sheep->speak (); delete sheep; }int main () { test01 (); return 0 ; } ### 多态案例: 计算器类 分别使用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 ```c++#include <iostream> using namespace std;class CalculatorNoraml { public : int getResult (string oper) { if (oper == "+" ) { return m_Num1 + m_Num2; } else if (oper == "-" ) { return (m_Num1 > m_Num2)? (m_Num1 - m_Num2) : (m_Num2 - m_Num1); } return 0 ; } int m_Num1; int m_Num2; };void test01 () { CalculatorNoraml test1; test1.m_Num1 = 10 ; test1.m_Num2 = 8 ; cout << "normal +:" << test1.getResult ("+" ) << endl; cout << "normal -:" << test1.getResult ("-" ) << endl; }class CalculatorAbstract { public : virtual int getResult () { return 0 ; } int m_Num1; int m_Num2; };class Add : public CalculatorAbstract { public : int getResult () { return m_Num2 + m_Num1; } };class Sub : public CalculatorAbstract { public : int getResult () { return (m_Num1 > m_Num2)? (m_Num1 - m_Num2) : (m_Num2 - m_Num1); } };void test02 () { CalculatorAbstract * abc = new Add; abc->m_Num1 = 10 ; abc->m_Num2 = 8 ; cout << "abstract +:" << abc->getResult () << endl; delete abc; abc = new Sub; abc->m_Num1 = 10 ; abc->m_Num2 = 8 ; cout << "abstract -:" << abc->getResult () << endl; delete abc; }int main () { test01 (); test02 (); return 0 ; }
输出:
1 2 3 4 normal +: 18 normal -:2 abstract +: 18 abstract -: 2