目录
一. 宏常量和const常量
#include<iostream>
#include<string>
#define MAX_NUX 100//宏变量定义,注意:不能加冒号
using namespace std;
int main() {
cout << MAX_NUX << endl;
const int MAX_NUM2 = 200;//const常量定义
cout << MAX_NUM2 << endl;
return 0;
}
二. 二维数组
#include<iostream>
using namespace std;
int main() {
//二维数组数组名
int arr[2][3] =
{
{1,2,3},
{4,5,6}
};
cout << "二维数组大小: " << sizeof(arr) << endl;
cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
//地址
cout << "二维数组首地址:" << arr << endl;
cout << "二维数组第一行地址:" << arr[0] << endl;
cout << "二维数组第二行地址:" << arr[1] << endl;
cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;//也就是二维数组首地址
cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
system("pause");
return 0;
}
三. 指针
1.定义、使用以及内存空间大小
#include<iostream>
using namespace std;
int main() {
//定义
int a = 10;
int* p = &a;
//使用
cout << p << endl;//输出地址
cout << *p << endl;//解引用
//指针内存空间大小:所有指针类型在32位操作系统下是4个字节
cout << sizeof(p) << endl;
cout << sizeof(char*) << endl;
cout << sizeof(float*) << endl;
cout << sizeof(double*) << endl;
system("pause");
return 0;
}
2. 空指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
int main() {
//指针变量p指向内存地址编号为0的空间
int * p = NULL;
//访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
cout << *p << endl;
system("pause");
return 0;
}
3. const修饰指针
const修饰指针有三种情况(本质上都是指针)
-
const修饰指针 --- 常量指针
-
const修饰常量 --- 指针常量
-
const即修饰指针,又修饰常量
#include<iostream> using namespace std; int main() { int a = 10; int b = 10; //const修饰的是指针,指针指向可以改,指针指向的值不可以更改 const int* p1 = &a; p1 = &b; //正确 //*p1 = 100; 报错 //const修饰的是常量,指针指向不可以改,指针指向的值可以更改 int* const p2 = &a; //p2 = &b; //错误 *p2 = 100; //正确 //const既修饰指针又修饰常量 const int* const p3 = &a; //p3 = &b; //错误 //*p3 = 100; //错误 system("pause"); return 0; }
4. 指针和数组
#include<iostream>
using namespace std;
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr; //指向数组的指针,注意:不用加取地址符!!1
cout << "第一个元素: " << arr[0] << endl;
cout << "指针访问第一个元素: " << *p << endl;
for (int i = 0; i < 10; i++)
{
//利用指针遍历数组
cout << *p << endl;
p++;
}
system("pause");
return 0;
}
5.万能指针(void *)
含义:
void*
就是一个通用指针,可以指向任意类型的指针。
使用:
如果将void*
类型指针指向其他类型指针,则需要强制类型转换,才能取出后指针的值。
int someInt = 10;
void* pvoid = &someInt;
int* pInt = (int*) pvoid; //这里需要强制类型转换
四.引用
1.基本使用
作用: 给变量起别名!!!(实际就是这个变量)
语法: 数据类型 &别名 = 原名
#include<iostream>
using namespace std;
int main() {
int a = 10;
//int& b = 10; //报错,非常量引用必须赋值变量
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;//注意:这里a也会被赋值为100
cout << "b = " << b << endl;
system("pause");
return 0;
}
2.注意事项
-
引用必须初始化
-
引用在初始化后,不可以更改引用了
#include<iostream> using namespace std; int main() { int a = 10; int b = 20; //int &c; //错误,引用必须初始化 int& c = a; //一旦初始化后,就不可以更改 c = b; //这是赋值操作,不是更改引用 cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; system("pause"); return 0; }
3. 引用传递
#include<iostream>
using namespace std;
//1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
4.函数返回值为引用类型
#include<iostream>
using namespace std;
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20;//静态变量,存放在全局区,全局区上的数据在程序结束后由系统释放
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;//第一次结果正确,因为编译器做了保留
cout << "ref = " << ref << endl;//第二次结果错误,因为局部变量a的内存已经被释放
//静态变量
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
//如果函数做左值,那么必须返回引用
test02() = 1000;//相当于a=1000
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
五. 函数提高
1.函数占位符
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){ }
作用:后续补充!!!
#include<iostream>
using namespace std;
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10, 10); //占位参数必须填补
system("pause");
return 0;
}
2. 函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
-
同一个作用域下
-
函数名称相同
-
函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
#include<iostream>
using namespace std;
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "func (double a)的调用!" << endl;
}
void func(int a, double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a, int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10, 3.14);
func(3.14, 10);
system("pause");
return 0;
}
注意事项2:
-
引用作为重载条件
-
函数重载碰到函数默认参数
#include<iostream>
using namespace std;
//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
cout << "func (int &a) 调用 " << endl;
}
void func(const int &a)
{
cout << "func (const int &a) 调用 " << endl;
}
//2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10) 调用" << endl;
}
void func2(int a)
{
cout << "func2(int a) 调用" << endl;
}
int main() {
int a = 10;
func(a); //会调用无const
func(10);//会调用有const
//func2(10); //报错,碰到默认参数产生歧义,需要避免
system("pause");
return 0;
}
六. 类和对象
1.struct和class区别
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
区别:
-
struct 默认权限为公共
-
class 默认权限为私有
#include<iostream>
using namespace std;
class C1
{
int m_A; //默认是私有权限
};
struct C2
{
int m_A; //默认是公共权限
};
int main() {
C1 c1;
//c1.m_A = 10; //错误,访问权限是私有
C2 c2;
c2.m_A = 10; //正确,访问权限是公共
system("pause");
return 0;
}
2. 创建对象的三种方式
- 隐式调用
- 显式调用
- new
#include<iostream>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数,注意:只有一个函数执行完才会执行析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test01() {
//隐式调用
Person p1;
Person p2(18);
//显示调用
Person p3=Person();
Person p4 = Person(20);
//new关键字
Person* p5 = new Person(22);
delete(p5);
}
int main() {
test01();
system("pause");
return 0;
}
前两种方式生成的对象:
内存分配到栈里(Stack),使用 “.” 调用对象的方法和属性。当程序离开对象的使用范围(如方法结束,一个程度块的最后{}),范围内的栈中的对象会自动删除,内存自动回收。
new方式生成的对象:
在堆中分配了内存,使用 “->” 调用对象的方法和属性。在堆中的对象不会自动删除,内存不会自动回收,所以new一个对象使用完毕,必须调用delete,释放内存空间。也就是说,new和delete必须成对出现.
3. " . " " -> " " :: "的区别:
1、A.B则A为对象或者结构体(this是指针不是对象)
2、A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针
3、::是作用域运算符,A::B表示作用域A中的名称B,A可以是名字空间、类、结构
4. 构造函数的分类及使用
-
按照参数分类分为: 有参和无参构造(默认构造函数)
-
按照类型分类分为: 普通构造和拷贝构造
注意事项:
- 调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明(在函数中声明一个函数,编译不会报错)
- 不能利用拷贝构造函数初始化匿名对象 编译器认为是对象声明
代码:
#include<iostream>
using namespace std;
#include <string>
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person(p5); 这就是匿名对象,注意是无左值
}
int main() {
test01();
test02();
system("pause");
return 0;
}
结果:
5. 匿名对象
特点:
当前行结束之后,系统会立刻回收匿名对象。
6. 拷贝构造函数的调用时机
C++中拷贝构造函数调用时机通常有三种情况
-
使用一个已经创建完毕的对象来初始化一个新对象
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
#include<iostream>
using namespace std;
#include <string>
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
}
//3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int*)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int*)&p << endl;
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
7. 构造函数调用规则
默认情况下,c++中至少一个类有三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
例如:
自己定义了一个拷贝构造函数:
#include<iostream>
using namespace std;
#include <string>
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
不定义拷贝构造函数:
#include<iostream>
using namespace std;
#include <string>
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
构造函数调用规则如下:
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造(不提供的意思是相当于不存在这个函数,想用只能我们自己去定义)
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
8. 深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
什么时候用到深拷贝?
涉及到在堆区开辟空间的变量,就需要用到深拷贝。
举个例子:
#include<iostream>
#include<string>
#include <fstream>
#include<windows.h>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age, int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);//堆区开辟的数据,需要手动释放
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);//栈满足先进后出,p1先构造,晚析构,所以析构顺序是先p2再p1,此时会调用默认拷贝构造函数
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
上面这段代码会报错,原因是先执行p2析构之后,再执行p1析构的时候出了问题,浅拷贝的时候p1.m_height和p2.m_height的值相同,并且都是指向同一个内存,不能被释放两次
这里调用的默认拷贝构造函数其实是这样:
解决办法就是提供一个拷贝构造函数进行深拷贝
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
9. this指针
this指针的用途:
-
当形参和成员变量同名时,可用this指针来区分
-
在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;
#include <string>
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;//这里age=age得到的结果出错
}
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//2、返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
10. 友元
友元的目的就是让一个函数或者类 访问另一个类中私有成员。
友元的关键字为 ==friend==
友元的三种实现
-
全局函数做友元
-
类做友元
-
成员函数做友元
10.1 全局函数做友元
#include<iostream>
using namespace std;
#include <string>
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
friend void goodGay(Building* building);
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
void goodGay(Building* building)
{
cout << "好基友正在访问: " << building->m_SittingRoom << endl;
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main() {
test01();
system("pause");
return 0;
}
10.2 类做友元
#include<iostream>
using namespace std;
#include <string>
class Building;
class goodGay
{
public:
goodGay();
void visit();
private:
Building* building;
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main() {
test01();
system("pause");
return 0;
}
10.3 成员函数做友元
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
void visit2();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
七. 模板
1.函数模板
语法:
template<typename T>
函数声明或定义
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母T
总结:
-
函数模板利用关键字 template
-
使用函数模板有两种方式:自动类型推导、显示指定类型
-
模板的目的是为了提高复用性,将类型参数化
#include<iostream>
using namespace std;
//交换整型函数
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
//交换浮点型函数
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
//利用模板实现交换
//1、自动类型推导
mySwap(a, b);
//2、显示指定类型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main() {
test01();
system("pause");
return 0;
}
注意事项:
-
使用 模板时必须确定出通用数据类型T,并且T只能够代表一种类型
2. 类模板
语法:
template<typename T>
类
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
#include<iostream>
using namespace std;
#include <string>
//类模板
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
void test01()
{
// 指定NameType 为string类型,AgeType 为 int类型
Person<string, int>P1("孙悟空", 999);
P1.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
八、继承
继承的好处:==可以减少重复的代码==
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
1. 继承方式
语法:
class 子类 : 继承方式 父类
继承方式一共有三种:
-
公共继承
-
保护继承
-
私有继承
一张图理解继承:
共有继承:父类的属性在子类中不变。
保护继承:public-》protected
私有继承: public-》private、protected-》private
总结:向下兼容,其中私有属性不会被继承。
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可访问 public权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass()
{
Son1 s1;
s1.m_A; //其他类只能访问到公共权限
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可访问 protected权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可访问
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可访问 private权限
m_B; //可访问 private权限
//m_C; //不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,son3的属性都变成私有,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A;
//m_B;
//m_C;
}
};
2. 继承中的对象模型
父类中所有非静态成员都会被子类继承下去
#include<iostream>
#include <string>
using namespace std;
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
//公共继承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
//父类中所有非静态成员束胸都会被子类继承下去
//父类私有成员属性是被编译器给隐藏了,因此访问不到,但是确实是被继承下去了
cout << "sizeof Son = " << sizeof(Son) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
3. 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需要加作用域
#include<iostream>
#include <string>
using namespace std;
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
总结:
-
子类对象可以直接访问到子类中同名成员
-
子类对象加作用域可以访问到父类同名成员
-
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
4. 继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需要加作用域
总结:同名静态成员有两种访问的方式(通过对象 和 通过类名)
#include<iostream>
#include <string>
using namespace std;
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
5. 菱形继承
概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
案例:
存在的问题:
菱形继承问题:
-
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
-
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
解决方法:
虚继承
代码:
class Animal
{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
虚表解析:
进入方式:
1.
2.进入到解决方案文件夹
3. dir
4.
解释:
此时子类继承的是虚指针,通过不同的偏移量指向了同一个地方
九、多态
9.1 动态多态和静态多态
多态分为两类
-
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
-
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
9.1.1动态多态
即运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
要实现动态多态,得满足两个条件:
-
有继承关系
-
子类重写父类中的虚函数
先看基类不是虚函数:
#include<iostream>
#include <string>
using namespace std;
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal& animal)
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
再看是虚函数:
#include<iostream>
#include <string>
using namespace std;
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal& animal)
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
9.1.2 纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” 。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
抽象类特点:
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class a
{
public:
virtual fun1();
virtual fun2();
.
.
.
virtual ...;
};class b : public a
{
fun1(){...;}
fun(){...;}
...
};class c : public a
{
...;
};
如果有很多类都继承了这个基类,那么每个对象中都要为创建基类消耗资源,此时出现了虚函数表。
9.1.3 虚函数表
对于有虚函数的类,编译器都会维护一张虚函数表(虚表),对象的前四个字节就是指向虚表的指针(虚表指针)。
虚函数表的创建分为两种情况:
无覆盖
基类中虚函数在派生类中不是虚函数
class Base
{
virtual void fun1();
virtual void fun2();
virtual void fun3();
}
class Derived : public Base
{
virtual void fun4();
virtual void fun5();
virtual void fun6();
}
虚函数表:
- 虚函数按声明顺序存在虚表中
- 在派生类中,前面是基类的虚函数,后面是派生类的虚函数
有覆盖
class Base
{
virtual void fun1();
virtual void fun2();
virtual void fun3();
}
class Derived : public Base
{
virtual void fun2();
virtual void fun3();
virtual void fun4();
}
虚函数表
- 先拷贝基类的虚函数表
- 如果派生类重写了基类的某个虚函数,就用派生类的虚函数替换虚表同位置的基类虚函数
- 跟上派生类自己的虚函数
通过基类的引用或指针调用,调用基类还是派生类的虚函数,要根据运行时根据指针或引用实际指向或引用的类型确定,调用非虚函数时,则无论基类指向的是何种类型,都调用基类的函数