C++学习

这篇博客详细介绍了C++的基础知识,包括宏常量、二维数组、指针(定义、空指针、const修饰、指针与数组、万能指针void*)、引用(基本使用、注意事项、引用传递、函数返回值为引用类型)、函数(占位符、重载)、类和对象(struct与class的区别、创建对象的三种方式、构造函数调用规则、深拷贝与浅拷贝)。此外,还讲解了模板(函数模板、类模板)的概念和使用。
摘要由CSDN通过智能技术生成

目录

一. 宏常量和const常量

二. 二维数组

三. 指针

1.定义、使用以及内存空间大小

2. 空指针

3. const修饰指针

4. 指针和数组

5.万能指针(void *)

四.引用

1.基本使用

2.注意事项

3. 引用传递 

4.函数返回值为引用类型

五. 函数提高

1.函数占位符

2. 函数重载

六. 类和对象

1.struct和class区别

2. 创建对象的三种方式

3. " . "  " -> "  " :: "的区别:

4. 构造函数的分类及使用

5. 匿名对象

6. 拷贝构造函数的调用时机

7.  构造函数调用规则

8.  深拷贝与浅拷贝

七. 模板

1.函数模板

语法:

2. 类模板


一. 宏常量和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修饰指针有三种情况(本质上都是指针)

  1. const修饰指针 --- 常量指针

  2. const修饰常量 --- 指针常量

  3. 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. 构造函数的分类及使用

  •  按照参数分类分为: 有参和无参构造(默认构造函数)

  •  按照类型分类分为: 普通构造和拷贝构造

注意事项:

  1. 调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明(在函数中声明一个函数,编译不会报错)
  2. 不能利用拷贝构造函数初始化匿名对象 编译器认为是对象声明

代码:

#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;
}

总结:

  1. 子类对象可以直接访问到子类中同名成员

  2. 子类对象加作用域可以访问到父类同名成员

  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

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. 菱形继承

概念:

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或者钻石继承

案例:

 存在的问题:

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

解决方法:

虚继承

代码:

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();
}

虚函数表

  • 先拷贝基类的虚函数表
  • 如果派生类重写了基类的某个虚函数,就用派生类的虚函数替换虚表同位置的基类虚函数
  • 跟上派生类自己的虚函数

通过基类的引用或指针调用,调用基类还是派生类的虚函数,要根据运行时根据指针或引用实际指向或引用的类型确定,调用非虚函数时,则无论基类指向的是何种类型,都调用基类的函数
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值