c++入门
- 引用
- **new、delete**
- 类和对象
- **类和对象的基本概念**
- **类的封装**
- **尽量把成员属性设置为私有的**
- **小练习**
- **圆的周长类案例**
- **学生类的案例**
- **汽车案例**
- **立方体案例**
- **点和圆案例**
- **构造函数和析构函数的简要概述**
- **构造函数和析构函数的简单调用**
- **构造函数和析构函数能够函数重载**
- **默认的构造函数和析构函数**
- **拷贝构造**
- **构造函数的分类和调用**
- **匿名对象**
- **拷贝构造函数的调用时机**
- **构造函数的调用规则**
- **多个对象的构造函数和析构函数**
- **深浅拷贝**
- **补充:初始化列表**
- **类对象作为类成员(初始化列表写法)**
- **静态成员变量**
- **成员变量和成员函数分开存储**
- **this指针**
- **空指针访问成员函数**
- **const修饰的成员函数**
- **友元**
- **友元类**
- **友元作为成员函数**
- **补充:explicit**
- 函数高级
- 运算符重载
- 继承
- 多态
引用
1.引用是做什么:和C语言的指针一样的功能,并且使语法更加简洁
2.引用是什么∶引用是给空间取别名
引用的语法
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
void test01()
{
//引用
int a = 10;
int &b = a;//给a的空间取别名叫b 不是取地址
cout << "a=" << a << endl;
cout << "b=" << b << endl;
b = 100;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
//指针
//int a1 = 10;
//int *p = &a;
//*p = 100;
}
//void func(int *a)
//{
// *a = 200;
//}
void func(int &b)//int &b=a
{
b = 200;
}
void test02()
{
int a = 10;
cout << "a=" << a << endl;
//func(&a);
func(a);
cout << "a=" << a << endl;
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
引用的注意事项
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int main()
{
//1、引用创建时必须要初始化
//int &a;err
//2、引用一旦初始化不能改变它的指向
int a = 10;
int &b = a;
//执行到这步时候a和b指向的是同一块内存
int c = 20;
b = c;//赋值操作 改变不了它的指向
cout << &a << endl;
cout << &b << endl;//地址一样
cout << &c << endl;
cout << endl;
cout << a << endl;
cout << b << endl;
cout << c << endl;
system("pause");
return EXIT_SUCCESS;
}
引用作为函数参数
#define _CRT_SECURE_NO_WARNINGS
#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;
}
这里第二次用指针进行交换为20 10 再用引用就是10 20 没什么问题
引用作为函数的返回值 111
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
#include <iostream>
using namespace std;
// 返回局部变量引用(错误示例,会导致悬空引用)
int& test01() {
int a = 10; // 局部变量
return a;
}
// 返回静态变量引用
int& test02() {
static int a = 20; // 静态变量位于全局区
return a;
}
// 返回动态分配内存的引用
int& test03() {
int* ptr = new int(30); // 动态分配内存
return *ptr;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
// 如果函数做左值,那么必须返回引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
// 如果函数的返回值是引用,这个函数调用可以作为左值
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
// 返回动态分配内存的引用
int& ref3 = test03();
cout << "ref3 = " << ref3 << endl;
// 释放动态分配的内存
//delete &ref3;
delete &ref3;
system("pause");
return 0;
}
在 test01() 函数中,确实有一个 int a = 10; 的局部变量,然后函数返回了对这个局部变量的引用。问题出在这里:
int& test01() {
int a = 10; // 局部变量
return a;
}
1、当函数执行完毕并返回时,局部变量 a 的生命周期结束,其内存空间被释放。引用实际上指向了一个不再存在的内存位置。这被称为悬垂引用(dangling reference),它是一种未定义行为。尽管某些编译器可能会生成警告,但这并不意味着代码的行为是可预测和正确的。
编译器可能在某些情况下(如简单的测试程序)可能会优化,使得代码运行看起来正常,但这是一种偶然的情况。在实际编程中,依赖于这种行为是不安全的,因为它可能导致难以调试和不一致的程序行为。
建议始终避免返回对局部变量的引用,以防止悬垂引用的问题。如果需要返回引用,可以考虑返回静态变量、全局变量或者通过动态内存分配来创建对象,并返回对动态分配内存的引用
2、而在函数 test02() 中,返回类型是 int&,即返回一个整型的引用。这是因为函数希望返回的是一个能够修改的左值,而不是一个右值或临时值。让我们解释一下为什么函数返回类型是 int&:返回引用的优势: 返回引用允许函数修改调用者提供的对象。如果函数返回的是对象的拷贝而不是引用,那么对该拷贝的修改不会影响原始对象。静态变量的生命周期长于函数调用: 在 test02() 中,a 是一个静态变量,它的生命周期长于函数调用。因此,将其引用返回给调用者是安全的,因为即使函数结束,静态变量 a 仍然存在。引用可以作为左值: 返回引用使得函数调用可以像变量一样用作左值。这意味着你可以在调用函数的同时对其返回的引用进行赋值操作。总的来说,将函数 test02() 的返回类型声明为 int& 是为了允许对静态变量 a 进行引用,以便在函数调用时能够修改该变量的值,并且可以让函数调用作为左值使用。
3、是的,当函数返回引用类型时,你必须使用一个引用来接收它。这是因为引用需要绑定到一个对象,而不是像值一样被拷贝。如果你不使用引用来接收返回的引用值,将无法有效地使用它。
int& ref2 = test02();
这里 ref2 是一个整型的引用,它接收了 test02() 函数返回的引用。现在,你可以使用 ref2 来访问和修改 test02() 返回的静态变量。
如果你不使用引用来接收返回的引用,比如这样:
test02(); // 错误,没有一个引用或变量来接收返回值
上述代码是错误的,因为 test02() 的返回值是一个引用,但没有任何东西来接收它。这会导致编译错误。
数组的引用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int main()
{
int arr[] = { 1,2,3,4,5 };
//第一种方法
//1、定义数组类型
//这段代码定义了一个数组类型MY_ARR,该数组类型包含5个int元素。通过使用typedef关键字,将 int[5] 声明为 MY_ARR。
typedef int(MY_ARR)[5];//数组类型
//2、建立引用
MY_ARR &arref = arr;//建立引用
//第二种方法
//直接定义引用
int(&arref2)[5] = arr;
//第三种方法
typedef int(&MY_ARR3)[5];//建立引用数组类型
MY_ARR3 arref3 = arr;
for (int i = 0; i < 5; i++)
{
cout << arref[i] << endl;
}
cout << endl;
for (int i = 0; i < 5; i++)
{
arref2[i] = 100 + i;
cout << arref2[i] << endl;
}
system("pause");
return EXIT_SUCCESS;
}
引用的本质
引用的本质在c++内部实现时一个指针常量
常量指针:指向常量的指针,是一个指向可改,值不可改的指针
指针常量:该指针是常量,是一个指向不可改,值可改的指针
//不可以通过指针间接修改常量值
//案例1 err
int main()
{
//常量
const int a = 10;
//a = 100;//err
int *p=&a;
*p=100;
cout<<a<<endl;
return 0;
}
#include <iostream>
using namespace std;
int main() {
// 常量
const int a = 10;
// 使用const_cast去除const属性
int* p = const_cast<int*>(&a);
*p = 100;
cout << "Modified a: " << a << endl;
return 0;
}
//案例2
int main()
{
int a=10;
int b=20;
const int* p=&a;//常量指针 值不可以被修改
p=&b;
cout<<*p<<endl;
//*p=100;//err
return 0;
}
这段代码是合法的,因为指针 p 是一个指向常量整型的指针,而不是指向常量的指针。这意味着指针 p 可以指向不同的常量,但不能通过指针 p 来修改所指向的值。
//案例3
int main()
{
int a=10;
int b=20;
int *const p=&a;//指针常量
//p=&b;//err
*p=200;
cout<<a<<endl;
}
//案例4
int main()
{
int a=10;
int b=20;
const int* const p=&a;
int** pp=&p;
*pp=&b;
cout<<*p<<endl;
}
复习完后 就是下面引用的本质:
引用一旦初始化就不可以发生改变
尽量用const来代替define
用define是不能够进行类型的检测的
而const时可以进行类型检测的
const和#define区别总结:
1、const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查
2、const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
指针的引用
c语言下的操作如下所示:
https://blog.csdn.net/m0_69093426/article/details/130178619
c语言下,被调函数申请一块堆区的空间,主调函数用这块空间需要高级的指针(二级指针)
c++下的操作如下所示
指针要么是四个字节 要么是八个字节 32 64
如图所示此时打印出来的p1就是翠花 如果你不好理解的话 可以把char看成一个整体理解即可
下面两个函数的流程图的过程如下:
**执行char mp=NULL,然后执行char * &tmp=mp,mp是char类型,这部相当于把char类型的mp取了别名叫tmp,然后就是字符串操作,再然后执行tmp=p,就是把p里面的值赋值给tmp,就能打印出小花了。**
有人可能会问为什么cout<<mp<<endl; 而不是cout<<*mp<<endl;
在C++中,使用 cout 输出一个指针时,它会输出指针所指向的字符数组的地址,而不是整个字符串。因此,如果你使用 *mp,它将输出指针所指向的字符数组的第一个字符,而不是整个字符串。
考虑以下示例:
char* mp = "Hello";
cout << mp << endl; // 输出 Hello
cout << *mp << endl; // 输出 H
第一行输出整个字符串 “Hello”,而第二行输出字符串的第一个字符 “H”。
所以,当你想要输出整个字符串时,应该使用 cout << mp << endl;,而不是 *mp。
当被调函数申请堆区空间且主调函数要拿这块空间要用的时候
1、通过返回值(只能传一个)
2、通过二级指针 通过参数进行转
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
void test01()
{
char* p =(char *)"翠花";
char * &p1 = p;
cout << p1 << endl;
}
//c语言下
//被调函数申请一块堆区的空间,主调函数用这块空间需要高级的指针(二级指针)
//或者通过返回值
//被调函数
//void func(char* *tmp)
void func(char* &tmp)
{
char *p;
p=(char *)malloc(64);
memset(p, 0, 64);
strcpy(p, "小花");
//*tmp = p;
tmp = p;
}
//主调函数
void test02()
{
char *mp = NULL;
//func(&mp);
func(mp);
cout << mp << endl;
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
常量引用
常量引用主要用来修饰形参,防止误操作(让编译器优化代码 创造一个临时变量)
在函数形参列表中,可以加const修饰形参,防止形参改变实参
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;//err 假如const之后就不能够修改原来main函数定义的值
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
//加入const就可以了,编译器优化代码 临时的值 int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //加入const后不可以修改变量
cout << ref << endl;
//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);
system("pause");
return 0;
}
//int& ref = 10; //err 引用本身需要一个合法的内存空间,因此这行错误
整型字面量 10 并没有分配内存空间,因此无法绑定到引用上。在编译时,这行代码会产生错误,因为它试图将一个引用绑定到一个不合法的内存空间
new、delete
基本使用
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete释放堆区数据
delete p;
//cout << *p << endl; //报错,释放的空间不可访问
system("pause");
return 0;
}
//堆区开辟数组
int main() {
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//释放数组 delete 后加 []
delete[] arr;
system("pause");
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
Maker()
{
cout << "构造函数" << endl;
}
Maker(int a)
{
cout << "有参构造函数" << endl;
}
~Maker()
{
cout << "析构函数" << endl;
}
};
void test01()
{
//用c语言方式申请堆区空间,不会调用构造函数
Maker *m = (Maker*)malloc(sizeof(Maker));
//对象释放时不会调用析构函数
}
void test02()
{
//用new方式申请堆区空间,会调用构造函数
Maker *m = new Maker;
//释放堆区空间,会调用类的析构函数
delete m;
m = NULL;
Maker *m2 = new Maker(10);
delete m2;
m2 = NULL;
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
堆区申请和释放数组空间
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//1、new创建基础类型数组
void test01()
{
//申请基础数据类型的数组
//初始化数组
//int *pInt = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };//不推荐
int *pInt = new int[10];
for (int i = 0; i < 10; i++)
{
pInt[i] = i + 1;
}
for (int i = 0; i < 10; i++)
{
cout << pInt[i] << " ";
}
cout << endl;
char *pChar = new char[64];
memset(pChar, 0, 64);
strcpy(pChar, "露琪亚");
cout << pChar << endl;
//注意:如果new时有中括号,那么delete也时要有中括号
//delete不加[]只是释放第一个元素
delete[] pInt;
delete[] pChar;
}
//2、new对象数组
class Maker
{
public:
Maker()
{
cout << "无参构造函数" << endl;
}
Maker(int a)
{
cout << "有参构造函数" << endl;
}
~Maker()
{
cout << "析构函数" << endl;
}
};
void test02()
{
//有多少个对象产生就要调用多少个构造函数
//这行代码创建了一个指向 Maker 对象数组的指针 ms,并使用 new 运算符在堆上分配了两个 Maker 对象的内存空间
Maker *ms = new Maker[2];//调用无参构造
delete[] ms;
//大部分编译器不支持这种写法,(聚合初始化)vs不支持
//Maker *ms2 = new Maker[2]{ Maker(10),Maker(20) };
}
//3、delete void*可能出错,不会调用对象的析构函数
void test03()
{
//这里new了一个Maker 有一个新的对象产生 所以调用了一次构造函数
void *m = new Maker;//c++编译器不认识这个指针(void *)指向哪个函数 因此不会调用析构函数
//如果用void*来接new的对象,那么delete时不会调用析构函数
delete m;//预处理 编译 汇编 链接
//这种编译方式叫静态联编
}
int main()
{
test01();
cout<<endl;
test02();
cout<<endl;
test03();
system("pause");
return EXIT_SUCCESS;
}
类和对象
类和对象的基本概念
类是自定义数据类型,是C语言的结构体进化而成的
对象是类实例化出的,用数据类型定义一个变量
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker //这个是类
{
public:
int a;//成员属性(成员变量)
void func()//成员方法(成员函数)
{
cout << "func" << endl;
}
};
int main()
{
Maker m; //m就是对象
system("pause");
return EXIT_SUCCESS;
}
类的封装
struct和class区别
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
区别:
struct 默认权限为公共
class 默认权限为私有
class C1
{
int m_A; //默认是私有权限
};
struct C2
{
int m_A; //默认是公共权限
};
int main() {
C1 c1;
//c1.m_A = 10; //错误,访问权限是私有
C2 c2;
c2.m_A = 10; //正确,访问权限是公共
cout<<c2.m_A<<endl;
system("pause");
return 0;
}
1、为什么要有封装?
封装是把属性(变量)和方法(函数)封装到类内,然后给这些数据赋予权限,防止乱调用函数和变量,出现错误,维护代码更方便
保护权限:儿子可以访问父亲保护的内容
私有权限:父亲有些东西不想让儿子知道 儿子不可以访问父亲的内容
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问 儿子可以访问父亲中的保护内容
//私有权限 private 类内可以访问 类外不可以访问 儿子不可以访问父亲的私有内容
class Person
{
//姓名 公共权限
public:
string m_Name;
//汽车 保护权限
protected:
string m_Car;
//银行卡密码 私有权限
private:
int m_Password;
public:
void func()
{
//类内
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
};
int main() {
Person p;
p.func();
cout << p.m_Name << endl;
p.m_Name = "李四";
cout << p.m_Name << endl;
//p.m_Car = "奔驰"; //保护权限类外访问不到
//p.m_Password = 123; //私有权限类外访问不到
system("pause");
return 0;
}
成员函数和成员变量都有权限
可以调用类的公共成员函数间接访问私有成员
给成员变量赋初值不受成员变量的访问权限影响。无论成员变量是 public、protected 还是 private,都可以在构造函数的成员初始化列表中为其赋初值
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
//封装:1、把属性和方法放到类中,给这些数据赋予权限
//类内
class Maker
{
public://公有权限
void set(string Name,int Id)
{
id = Id;
name = Name;
}
void printMaker()
{
cout << "id=" << id << endl << "name=" << name << endl;
}
void get()
{
cout << "Maker a=" << a << endl;
}
private://私有权限
int id;
string name;
protected://保护权限
int a;
};
//继承 公有继承
class Son :public Maker
{
public:
void func()
{
//下面的a是从父类复制过来的
a = 20;//子类的类内可以访问父类的保护权限的成员
//id = 1;//err 子类的类内不可以访问父类的私有权限的成员
}
void getS()
{
cout << "Son a=" << a << endl;
}
};
void test01()
{
Maker m;
m.set("露琪亚", 1);
m.printMaker();
}
void test02()
{
Maker m;
Son s;
s.func();
m.get();
s.getS();
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
Maker的a和Son的a不是同一个a
这里的Maker的a是由于没有进行初始化 而Son的a能打印出值是因为Maker的a虽然打印出来的是乱码 Son的a对Maker的a进行了修改 所以Son的a不是乱码
尽量把成员属性设置为私有的
class Person {
public:
//姓名设置可读可写
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//获取年龄
int getAge() {
return m_Age;
}
//设置年龄
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "你个老妖精!" << endl;
return;
}
m_Age = age;
}
//情人设置为只写
void setLover(string lover) {
m_Lover = lover;
}
private:
string m_Name; //可读可写 姓名
int m_Age; //只读 年龄
string m_Lover; //只写 情人
};
int main() {
Person p;
//姓名设置
p.setName("张三");
cout << "姓名: " << p.getName() << endl;
//年龄设置
p.setAge(50);
cout << "年龄: " << p.getAge() << endl;
//情人设置
p.setLover("苍井");
//cout << "情人: " << p.m_Lover << endl; //只写属性,不可以读取
system("pause");
return 0;
}
尽量把属性设置为私有权限
1、可以控制属性的读写权限
2、可赋予客户端访问数据的一致性
3、可以保护属性的合法性
小练习
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
class Maker
{
public:
void Init()
{
name = "露琪亚";
age = 18;
}
//set get方法
void setN(string mName)
{
name = mName;
}
string getN()
{
return name;
}
void setA(int mAge)
{
if (mAge >= 0 && mAge <= 100)
{
age = mAge;
}
}
int getA()
{
return age;
}
//打印方法
void printMaker()
{
cout << "name: " << name << " age: " << age << endl;
}
private:
string name;
int age;
};
int main()
{
Maker m;
m.Init();
m.printMaker();
m.setN("一护");
m.setA(30);
m.printMaker();
cout << "name:" << m.getN() << " age:" << m.getA() << endl;
m.getA();
system("pause");
return EXIT_SUCCESS;
}
圆的周长类案例
类中的属性和行为 我们统一称为 成员
属性 成员属性 成员变量
行为 成员函数 成员方法
//圆周率
const double PI = 3.14;
//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物
//封装一个圆类,求圆的周长
//class代表设计一个类,后面跟着的是类名
class Circle
{
public: //访问权限 公共的权限
//属性
int m_r;//半径
//行为
//获取到圆的周长
double calculateZC()
{
//2 * pi * r
//获取圆的周长
return 2 * PI * m_r;
}
};
int main() {
//通过圆类,创建圆的对象
// c1就是一个具体的圆
Circle c1;
c1.m_r = 10; //给圆对象的半径 进行赋值操作
//2 * pi * 10 = = 62.8
cout << "圆的周长为: " << c1.calculateZC() << endl;
system("pause");
return 0;
}
学生类的案例
通过行为给属性进行赋值案例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Student
{
public://公有
void setName(string Name)//成员方法 成员函数
{
name = Name;
}
void setId(int Id)
{
id = Id;
}
void myprint()
{
cout << "姓名:" << name << "\n学号" << id << endl;
}
private://私有权限
string name;//成员属性
int id;//成员属性
};
int main()
{
Student s;
s.setName("卡卡罗特");
s.setId(1);
s.myprint();
system("pause");
return EXIT_SUCCESS;
}
汽车案例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
class Car
{
public:
void init()
{
type = "BMW";
Size = 5;
Color = "白色";
}
void printCar()
{
cout << "品牌:" << type << " 大小:" << Size << endl;
}
private:
string type;
int Size;
string Color;
};
class SonCar :public Car
{
public:
//写
void setType(string Type)
{
type = Type;
}
void setMyT(int t)
{
MyT = t;
}
void setMynum(int num)
{
Mynum = num;
}
//读
string getT()
{
return type;
}
int getMyT()
{
return MyT;
}
int getMynum()
{
return Mynum;
}
void printConSar()
{
cout << "品牌:" << type << " 吨位:" << MyT << " 轮子个数:" << Mynum << endl;
Car::init();
Car::printCar();
}
private:
string type;
int MyT;
int Mynum;
};
void test01()
{
Car c;
c.init();
c.printCar();
}
void test02()
{
SonCar sc;
sc.setType("玛莎拉蒂");
sc.setMyT(20);
sc.setMynum(8);
sc.printConSar();
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
立方体案例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Cube
{
public:
//初始化变量
void init()
{
mL = 0;
mW = 0;
mH = 0;
}
//set
void setL(int l)
{
mL = l;
}
void setW(int w)
{
mW = w;
}
void setH(int h)
{
mH = h;
}
//get
int getL()
{
return mL;
}
int getW()
{
return mW;
}
int getH()
{
return mH;
}
//求立方体的面积
int caculateS()
{
return 2 * mL*mW + 2 * mL*mH + 2 * mW*mH;
}
//求立方体的体积
int calculateV()
{
return mL * mH * mW;
}
//成员函数
//这里在类里进行的 因此只需要传一个参数就行了
bool CompareCube(Cube &cube)
{
if ( getL() == cube.getL() && getW()== cube.getW() && getH() == cube.getH() )
{
return true;
}
else
{
return false;
}
}
private:
int mL;
int mW;
int mH;
};
//全局函数
bool IsCompareCube(Cube &c1,Cube &c2)
{
if (c1.getH()==c2.getH() && c1.getW() == c2.getW() && c1.getL() == c2.getL())
{
return true;
}
else
{
return false;
}
}
int main()
{
Cube c1, c2;
//初始化对象
c1.init();
c2.init();
//设置立方体的长宽高
c1.setL(10);
c1.setW(20);
c1.setH(30);
c2.setL(10);
c2.setW(20);
c2.setH(30);
//利用成员函数进行判断
if (c1.CompareCube(c2))
{
cout << "两个立方体相等" << endl;
}
else
{
cout << "两个立方体不相等" << endl;
}
//利用全局函数进行判断
if (IsCompareCube(c1,c2))
{
cout << "两个立方体相等" << endl;
}
else
{
cout << "两个立方体不相等" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
成员函数写在类里边,类里存在一个立方体的各个属性,所以传入一个立方体进去就可以了与存入在成员函数类里相比较
点和圆案例
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Point
{
public:
void setx(int x)
{
c_x = x;
}
int getx()
{
return c_x;
}
void sety(int y)
{
c_y = y;
}
int gety()
{
return c_y;
}
//建议将属性设置为私有,对外提供接口
private:
int c_x;
int c_y;
};
class Circle
{
public:
void setr(int r)
{
c_R = r;
}
int getr()
{
return c_R;
}
//圆心的设计
void setcenter(Point center)
{
c_center = center;
}
Point getcenter()
{
return c_center;
}
private:
int c_R;
Point c_center;
};
//判断
void isInCircle(Circle &c, Point &p)
{
int distance =
(c.getcenter().getx() - p.getx()) * (c.getcenter().getx() - p.getx()) +
(c.getcenter().gety() - p.gety()) * (c.getcenter().gety() - p.gety());
int rdistance = c.getr() * c.getr();
if (distance == rdistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rdistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main(void)
{
//设计半径
Circle c1;
c1.setr(10);
//设计圆心
Point center;
center.setx(10);
center.sety(0);
c1.setcenter(center);//这里不是值传递 这里的center是Point类型
//设计点
Point p1;
p1.setx(10);
p1.sety(10);
//调用判断
isInCircle(c1, p1);
return 0;
}
分文件编写:
Circle.h
#pragma once
#include "Point.h"
#include <cmath>//数据公式的库
#include <iostream>
using namespace std;
class Circle
{
public:
void SetR(int r);
void SetHear(Point &p);
void SetHear(int x, int y);
int getR();
Point getHear();
//判断点和圆的关系
void isPointAndCircle(Point &p);
private:
int mR;//半径
Point mHear;//圆心
};
Point.h
#pragma once
class Point
{
public:
void setX(int x);
void setY(int y);
int getX();
int getY();
private:
int mX;
int mY;
};
Point.c
#include "Point.h"
void Point::setX(int x)
{
mX = x;
}
void Point::setY(int y)
{
mY = y;
}
int Point::getX()
{
return mX;
}
int Point::getY()
{
return mY;
}
Circle.c
#include "Circle.h"
void Circle::SetR(int r)
{
mR = r;
}
void Circle::SetHear(Point &p)
{
mHear.setX(p.getX());
mHear.setY(p.getY());
}
void Circle::SetHear(int x, int y)
{
mHear.setX(x);
mHear.setY(y);
}
int Circle::getR()
{
return mR;
}
Point Circle::getHear()
{
return mHear;
}
//判断点和圆的关系
void Circle::isPointAndCircle(Point &p)
{
//获取圆心和点之间的距离的平方
double distance = pow((p.getX() - mHear.getX()), 2) + pow((p.getY() - mHear.getY()), 2);
//半径的平方
double tmpR = pow(mR, 2);
//
if (distance>tmpR)
{
cout << "点在圆外" << endl;
}
else if (distance == tmpR)
{
cout << "点在圆上" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
30 点和圆的案例.cpp
#include "Circle.h"
void Circle::SetR(int r)
{
mR = r;
}
void Circle::SetHear(Point &p)
{
mHear.setX(p.getX());
mHear.setY(p.getY());
}
void Circle::SetHear(int x, int y)
{
mHear.setX(x);
mHear.setY(y);
}
int Circle::getR()
{
return mR;
}
Point Circle::getHear()
{
return mHear;
}
//判断点和圆的关系
void Circle::isPointAndCircle(Point &p)
{
//获取圆心和点之间的距离的平方
double distance = pow((p.getX() - mHear.getX()), 2) + pow((p.getY() - mHear.getY()), 2);
//半径的平方
double tmpR = pow(mR, 2);
//
if (distance>tmpR)
{
cout << "点在圆外" << endl;
}
else if (distance == tmpR)
{
cout << "点在圆上" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
30 点和圆的案例.c
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "Circle.h"
//全局实现点和圆的关系
void G_isPointAndCircle(Circle &c,Point &p)
{
//获取点和圆之间的距离
double distance = pow((p.getX() - c.getHear().getX()), 2) + pow((p.getY() - c.getHear().getY()), 2);
//半径的平方
double tmpR = pow(c.getR(), 2);
//比较
if (distance > tmpR)
{
cout << "点在圆外" << endl;
}
else if (distance == tmpR)
{
cout << "点在圆上" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main()
{
//实例化点对象
Point p;
p.setX(10);
p.setY(10);
//实例化圆对象
Circle c;
c.SetHear(10, 20);
c.SetR(10);
c.isPointAndCircle(p);
G_isPointAndCircle(c,p);
system("pause");
return EXIT_SUCCESS;
}
在这个 Circle 类中,有两个重载的 SetHear 方法,其目的是为了提供更灵活的方式来设置圆心坐标。
void SetHear(Point &p);:
这个方法接受一个 Point 对象作为参数,用于设置圆的圆心坐标。通过传递一个 Point 对象,可以直接将圆心设置为另一个点的坐标。
void SetHear(int x, int y);:
这个方法接受两个整数参数 x 和 y,用于设置圆的圆心坐标。通过传递两个整数,可以直接指定圆心的 x 和 y 坐标。
这两个方法的存在使得在设置圆心坐标时具有更大的灵活性,用户可以选择使用 Point 对象或者直接指定坐标值。
构造函数和析构函数的简要概述
当对象产生时,必须初始化成员变量,当对象销毁前,必须清理对象.
初始化用构造函数,清理用析构函数,这两个函数是编译器调用 初始化的作用和析构函数的作用
构造函数点和析构函数的注意:
构造函数和析构函数的权限必须是公有的
构造函数可以重载
构造函数没有返回值,不能用void,构造函数可以有参数,析构函数没有返回值,不能用void,没有参数
有对象产生必然会调用构造函数,有对象销毁必然会调用析构函数。
有多少个对象产生就会调用多少次构造函数,
有多少个对象销毁就会调用多少次析构函数
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号 ~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
构造函数和析构函数的简单调用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
//构造函数的作用初始化成员变量 是编译器去调用的
Maker()
{
a = 10;
cout << "构造函数" << endl;
}
//析构函数,在对象销毁前编译器调用析构函数
~Maker()
{
cout << "析构函数" << endl;
}
public:
int a;
};
void test01()
{
//实例化对象,内部做了两件事
//1、分配空间
//2、调用构造函数进行初始化
Maker m;//栈区
int b = m.a;
cout << b << endl;
}
int main()
{
test01();
Maker m2;
system("pause");
return EXIT_SUCCESS;
}
这里由于system(“pause”);暂停了,还没有调用析构函数
暂停结束后就调用了析构函数
test01 函数被调用,创建了一个名为 m 的 Maker 类对象,分配在栈区。在对象创建时,构造函数被调用,打印了 “构造函数” 并将成员变量 a 初始化为 10。然后,取出 a 的值并打印,输出为 “10”。
test01 函数执行完毕,栈上的局部变量 m 被销毁,调用了 m 的析构函数,打印了 “析构函数”。
在 main 函数中,创建了一个名为 m2 的 Maker 类对象,同样分配在栈区。构造函数被调用,打印了 “构造函数” 并将成员变量 a 初始化为 10。
main 函数执行完毕,程序即将退出,栈上的局部变量 m2 被销毁,调用了 m2 的析构函数,打印了 “析构函数”。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
//构造函数的作用初始化成员变量 是编译器去调用的
Maker()
{
a = 10;
cout << "构造函数" << endl;
}
//析构函数,在对象销毁前编译器调用析构函数
~Maker()
{
cout << "析构函数" << endl;
}
public:
int a;
};
void test01()
{
//实例化对象,内部做了两件事
//1、分配空间
//2、调用构造函数进行初始化
Maker m;//栈区
int b = m.a;
cout << b << endl;
}
//析构函数的作用
class Maker2
{
public:
//有参构造
Maker2(const char* name,int age)
{
cout << "有参构造" << endl;
//从堆区空间申请
pName = (char *)malloc(strlen(name) + 1);//+1 因为/0
strcpy(pName, name);
mAge = age;
}
void printMaker2()
{
cout << "name:" << pName << " age:" << mAge << endl;
}
~Maker2()
{
cout << "析构函数001" << endl;
//释放堆区空间
if (pName!=NULL)
{
free(pName);
pName = NULL;
}
}
private:
char* pName;
int mAge;
};
void test02()
{
Maker2 m2("贝吉塔", 18);
m2.printMaker2();
}
int main()
{
//test01();
test02();
Maker m2;
system("pause");
return EXIT_SUCCESS;
}
在 test02() 函数中,创建了一个 Maker2 对象 m2,并调用了它的 printMaker2() 函数,然后该对象离开了作用域。因为 Maker2 类定义了析构函数,在对象离开作用域时会自动调用析构函数。在析构函数中,pName 所指向的堆区空间被释放了,因为在析构函数中执行了 free(pName);。所以,在 test02() 函数执行完毕后,堆区的数据已经被释放了。
一旦 pName 所指向的堆区空间被释放(在 Maker2 对象的析构函数中),pName 将不再指向有效的内存地址。在释放堆区空间后,通常会将指针设置为 NULL,以避免野指针的问题。
构造函数和析构函数能够函数重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
//构造函数的作用初始化成员变量 是编译器去调用的
Maker()
{
a = 10;
cout << "构造函数" << endl;
}
//析构函数,在对象销毁前编译器调用析构函数
~Maker()
{
cout << "析构函数" << endl;
}
public:
int a;
};
void test01()
{
//实例化对象,内部做了两件事
//1、分配空间
//2、调用构造函数进行初始化
Maker m;//栈区
int b = m.a;
cout << b << endl;
}
//析构函数的作用
class Maker2
{
public:
//有参构造
Maker2(const char* name,int age)
{
cout << "有参构造" << endl;
//从堆区空间申请
pName = (char *)malloc(strlen(name) + 1);
strcpy(pName, name);
mAge = age;
}
void printMaker2()
{
cout << "name:" << pName << " age:" << mAge << endl;
}
~Maker2()
{
cout << "析构函数001" << endl;
//释放堆区空间
if (pName!=NULL)
{
free(pName);
pName = NULL;
}
}
private:
char* pName;
int mAge;
};
class Maker3
{
public://构造函数和析构函数必须是公有权限
//构造函数可以重载
Maker3()//无参构造函数
{
cout << "Maker3的无参构造" << endl;
}
Maker3(int a)//有参构造函数
{
cout << "Maker3的有参构造" << endl;
}
~Maker3()
{
cout << "析构函数" << endl;
}
};
void test02()
{
Maker2 m2("贝吉塔", 18);
m2.printMaker2();
}
void test03()
{
Maker3 m;//当构造函数私有时,实例化不了对象
//有对象产生必然会调用构造函数,有对象销毁析构函数
//有多少个对象产生就会调用多少次构造函数
//有多少个对象销毁就会调用多少次析构函数
Maker3 m2(10);
}
int main()
{
//test01();
//test02();
test03();
//Maker m2;
system("pause");
return EXIT_SUCCESS;
}
默认的构造函数和析构函数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
//Maker()//默认的构造函数,函数体是空的
//{
//}
//~Maker()//默认的析构函数,函数体是空的
//{
//}
//编译器默认提供默认的构造函数和析构函数
void printfMaker()
{
a = 100;
cout << "a=" << a << endl;
}
private:
int a;
};
void test01()
{
Maker m;
m.printfMaker();
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
拷贝构造
拷贝构造必须要引用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
Maker()
{
cout << "无参构造函数" << endl;
a = 20;
}
//拷贝构造函数
Maker(const Maker &m)
{
cout << "拷贝构造函数" << endl;
a = m.a;
}
//打印函数
void printMaker()
{
cout << "a=" << a << endl;
}
private:
int a;
};
void test01()
{
Maker m1;
m1.printMaker();
//用一个已有的对象去初始化另一个对象
Maker m2(m1);
m2.printMaker();
}
class Maker2
{
public:
Maker2()
{
cout << "无参构造函数" << endl;
a = 20;
}
//编译器提供了默认的拷贝构造函数
//Maker2(const Maker2 &m)
//{
// //默认拷贝构造函数进行了成员变量的简单拷贝
// //默认拷贝构造函数进行了成员变量的简单拷贝
// a = m.a;
//}
//打印函数
void printMaker()
{
cout << "a=" << a << endl;
}
private:
int a;
};
//拷贝构造函数中形参要用引用
void test02()
{
Maker2 m1;
m1.printMaker();
Maker2 m2(m1);//这里编译器自动调用 所以是不会打印出来数据的
m2.printMaker();
}
class Maker3
{
public:
Maker3(int Ma)
{
cout << "有参构造函数" << endl;
ma = Ma;
}
Maker3(const Maker3 &m)
{
cout << "拷贝构造函数" << endl;
}
private:
int ma;
};
void test03()
{
Maker3 m1(10);//调用有参构造
Maker3 m2(m1);//调用拷贝构造
Maker3 m3 = m1;//调用拷贝构造 编译器会优化为Maker3 m3(m1);
}
int main()
{
test01();
test02();
test03();
system("pause");
return EXIT_SUCCESS;
}
如果拷贝构造函数中的形参不是引用会发生什么事情呢?
Maker3(const Maker3 m) const Maker3 m=m1; //编译器会把这段代码当成 :const Maker3 m(m1)
{
cout << "拷贝构造函数" << endl;
}
1、Maker3 m2(m1);
2、const Maker3 m=m1;
3、const Maker3 m(m1);
……死循环
构造函数的分类和调用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
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);
//Person p2(p1);//调用拷贝构造
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明 写了这个括号并没有创造对象
//Person p2();
cout << "------------" << endl;
//2.2 显式法
Person p2 = Person(10);
cout << "------------" << endl;
Person p3 = Person(p2);
cout << "------------" << endl;
//下面的匿名对象是调用了有参构造函数
Person(10);//单独写就是匿名对象 当前行结束之后,马上析构
//不要用拷贝构造函数 初始化匿名对象 编译器会认为创造了新的对象 Person(p3)===Person p3
//Person(p3) //err
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
}
int main() {
test01();
cout << "-----------" << endl;
test02();
system("pause");
return 0;
}
匿名对象
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
Maker()
{
cout << "无参构造函数" << endl;
}
Maker(int a)
{
cout << "有参构造函数" << endl;
}
//拷贝构造函数
Maker(const Maker &m)
{
cout << "拷贝构造函数" << endl;
}
~Maker()
{
cout << "析构函数" << endl;
}
//默认的赋值函数
};
void test01()
{
//Maker();//匿名对象的生命周期在当前行
Maker(10);//这也是匿名对象
//Maker m1(10);
//如果匿名对象有名字来接就不是匿名对象了
Maker m1 = Maker();//这不是匿名对象 生命周期在函数体结束
cout << "test01函数结束" << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
Maker m1 = Maker(); 这样的语法,实际上是在调用类 Maker 的默认构造函数来创建对象 m1。Maker() 的部分表示创建一个匿名对象,并将其赋值给 m1。这种语法的效果与直接写 Maker m1; 是相同的,两者都会调用 Maker 类的默认构造函数。这是因为在没有提供具体参数的情况下,编译器会默认调用默认构造函数。
因此,Maker m1 = Maker(); 和 Maker m1; 是等效的,都是使用默认构造函数创建对象。
拷贝构造函数的调用时机
test03在Debug下的调用 会调用拷贝构造
test03在vs下的 Realease下的调用,不会调用拷贝函数
这种模式下编译器会认为m和m1是同一个对象
qt也不调用
C++中拷贝构造函数调用时机通常有三种情况:
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式返回局部对象
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
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; //拷贝构造
//Person newman3;
//newman3 = 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();
cout << "----------------" << endl;
test02();
cout << "----------------" << endl;
test03();
system("pause");
return 0;
}
局部对象的特点是函数执行完会被释放掉,下面这段代码return p1返回的不是p1本身,而且调用 doWork2() 后,返回的 Person 对象会直接被初始化到变量 p 中。这种情况下,由于没有创建新的对象,而是将返回的对象直接赋值给了 p,因此不会调用无参构造函数。
doWork2 函数以值的方式返回了局部对象 p1。这是因为函数的返回类型是 Person,而不是 Person& 或 Person*。当一个函数返回一个非引用类型的对象时,实际上会触发对象的拷贝构造函数,将局部对象的副本传递给调用方。在 test03 函数中,调用了 doWork2 函数并将返回的 Person 对象赋值给了 p。这意味着会触发 Person 类的拷贝构造函数,将 doWork2 返回的局部对象的副本传递给 p
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
那为什么先打印的是拷贝构造 然后函数执行完 调用了一次析构函数?
答:在这段代码中,首先 doWork2() 函数创建了一个 Person 对象 p1,然后返回这个对象。在 test03() 函数中,将 doWork2() 返回的对象赋值给了 Person p。这里发生了拷贝构造,因为返回的对象需要复制一份给 p。
所以,先调用拷贝构造函数,然后再调用析构函数的原因是:
1.调用拷贝构造函数是因为返回的对象需要复制给 p,而拷贝构造函数的作用就是创建一个新对象并使用另一个对象的值来初始化它。
2.调用析构函数是因为 doWork2() 返回的临时对象超出了作用域,所以需要销毁它。
简单来说第一个打印析构函数是无参构造函数的析构
构造函数的调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
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;
}
void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}
int main() {
test01();
system("pause");
return 0;
}
多个对象的构造函数和析构函数
1、如果类有成员对象,那么先调用成员对象的构造函数,再调用本身的构造函数
析构函数的调用顺序反之
2、成员对象的构造函数调用和定义顺序一样
3、注意,如果有成员对象那么实例化对象时,必须保证成员对象的构造和析构能被调用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class BMW
{
public:
BMW()
{
cout << "BMW构造" << endl;
}
~BMW()
{
cout << "BMW析构" << endl;
}
};
class Buick
{
public:
Buick()
{
cout << "Buick构造" << endl;
}
~Buick()
{
cout << "Buick析构" << endl;
}
};
class Maker
{
public:
Maker()
{
cout << "Maker构造" << endl;
}
~Maker()
{
cout << "Maker析构" << endl;
}
private:
BMW bmw;//成员对象
Buick bui;//成员对象
};
void test01()
{
Maker m;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
这里我在第一个类传参数,下面进行调用需要在构造函数中加:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class BMW
{
public:
BMW(int a)
{
cout << "BMW构造 " << a << endl;
}
~BMW()
{
cout << "BMW析构" << endl;
}
};
class Buick
{
public:
Buick()
{
cout << "Buick构造" << endl;
}
~Buick()
{
cout << "Buick析构" << endl;
}
};
class Maker
{
public:
Maker():bmw(10)//这里用了初始化队列 bmw(10)实际上调用了BMW的有参构造 因为bmw是BMW类型
{
cout << "Maker构造" << endl;
}
~Maker()
{
cout << "Maker析构" << endl;
}
private:
BMW bmw;//成员对象
Buick bui;//成员对象
};
//1、如果类有成员对象,那么先调用成员对象的构造函数,再调用本身的构造函数
//析构函数的调用顺序反之
//2、成员对象的构造函数调用和定义顺序一样
//3、注意,如果有成员对象那么实例化对象时,必须保证成员对象的构造和析构能被调用
void test01()
{
Maker m;
}
//初始化列表时调用成员对象的指定构造函数
void test02()
{
Maker m;
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class BMW
{
public:
BMW(int a)
{
cout << "BMW构造 " << a << endl;
}
~BMW()
{
cout << "BMW析构" << endl;
}
};
class Buick
{
public:
Buick(int b,int c)
{
cout << "Buick构造 " << b << " " << c << endl;
}
~Buick()
{
cout << "Buick析构" << endl;
}
};
class Maker
{
public:
//初始化列表
//注意:初始化列表只能写在构造函数里面
//如果有多个对象需要指定调用某个构造函数,用逗号隔开
//Maker(int a):bmw(a),bui(10,20)
Maker(int a,int b,int c):bmw(a),bui(b,c)
{
cout << "Maker构造" << endl;
}
//注意2:如果使用了初始化列表,那么所以的构造函数都要写初始化里列表
//初始化
Maker(const Maker &m):bmw(40),bui(10,20)
{
}
~Maker()
{
cout << "Maker析构" << endl;
}
private:
BMW bmw;//成员对象
Buick bui;//成员对象
};
void test01()
{
//Maker m;
}
//初始化列表时调用成员对象的指定构造函数
void test02()
{
//Maker m(20);
Maker m(20,10,20);
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
深浅拷贝
浅拷贝:简单的赋值拷贝操作 浅拷贝带来的问题是堆区内存会被重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝案例及问题:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
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(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);//这里解引用解出的是160这个数据 相当于重新在堆区开辟了一块空间
}
//析构函数
~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);
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;
}
浅拷贝意味着只复制对象的成员变量的值,而不会复制动态分配的资源。对于类中简单的数据成员,如基本数据类型或指针,浅拷贝通常是足够的。但是,如果类中包含指针成员,并且这些指针指向动态分配的内存,则默认的浅拷贝可能会导致问题,因为两个对象会共享同一块内存,这可能会导致在一个对象被销毁时出现悬空指针或者内存泄漏(重复释放同一块空间)
浅拷贝释放内存空间所带来的问题:
用深拷贝来解决此类问题:
补充:初始化列表
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person
{
public:
//传统初始化操作
//Person(int a, int b, int c)
//{
// m_A = a;
// m_B = b;
// m_C = c;
//}
//利用初始化列表来初始化属性
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
//Person p(10, 20, 30);
Person p(30,20,10);
cout << "m_A= " << p.m_A << endl;
cout << "m_B= " << p.m_B << endl;
cout << "m_C= " << p.m_C << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class BMW
{
public:
BMW(int a)
{
cout << "BMW构造 " << a << endl;
}
~BMW()
{
cout << "BMW析构" << endl;
}
};
class Buick
{
public:
Buick(int b, int c)
{
cout << "Buick构造 " << b << " " << c << endl;
}
~Buick()
{
cout << "Buick析构" << endl;
}
};
class Maker
{
public:
Maker(int a, int b, int c) : bmw(a), bui(b, c)
{
cout << "Maker构造" << endl;
}
Maker(const Maker &m) : bmw(m.bmw), bui(m.bui)
{
cout << "Maker拷贝构造" << endl;
}
~Maker()
{
cout << "Maker析构" << endl;
}
private:
BMW bmw; //成员对象
Buick bui; //成员对象
};
void test02()
{
Maker m(20, 10, 20); // 创建一个Maker对象
Maker n(m); // 使用拷贝构造函数创建另一个Maker对象
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
类对象作为类成员(初始化列表写法)
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如:
class A {}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
#include<iostream>
#include<string>
using namespace std;
class Phone
{
public:
Phone(string p)
{
Phonename = p;
cout << "Phone的构造函数调用" << endl;
}
~Phone()
{
cout << "Phone的析构函数调用" << endl;
}
string Phonename;
};
class Person
{
public:
//Phone Personphone = pname 隐式转换法
/*
pname是string类型的 经过隐式转换法之后相当于调用了Phone的有参构造函数
这就是Personphone是Phone的数据类型 而pname是string的数据类型 两个数据类型虽然不同 但是编译器没有报错
*/
Person(string name, string pname):Personname(name), Personphone(pname)
{
cout << "Person的构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
string Personname;
Phone Personphone;
};
void test()
{
Person p("张三", "华为");
cout << p.Personname<< endl;
cout << p.Personphone.Phonename<< endl;
}
int main(void)
{
test();
system("pause");
return 0;
}
在其它类的对象作为本类中的成员的时候,会先构造其它类的构造函数,再构造本类的构造函数
析构的顺序与构造相反
静态成员变量
静态成员
静态成员就是在成员变量和成员函数前面加上关键字啊static,称为静态成员。
静态成员分为:
1、静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
2、静态成员函数
所有成员共享同一个函数
静态成员函数只能访问静态成员变量
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
static int m_A; //静态成员变量
private:
static int m_B; //静态成员变量也是有访问权限的
};
//声明 要加作用域
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象进行访问
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;//第二个对象使用 如果修改 相应的值也会被修改 这就是静态成员的特点
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名进行访问
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl;//err 私有权限访问不到
}
int main()
{
test01();
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}
static int m_A; //静态成员变量
int m_B; //
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//静态成员函数两种访问方式
//1、通过对象
Person p1;
p1.func();
//2、通过类名
Person::func();
//Person::func2(); //私有权限访问不到
}
int main() {
test01();
system("pause");
return 0;
}
成员变量和成员函数分开存储
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//成员变量和成员函数是分开存储的
class Person
{
int m_A;//非静态成员属于类对象上的。
static int m_B;//静态的成员变量不属于类的对象上。
void func() {}//非静态成员函数不属于类的对象上
static void func2() {}//静态成员函数不属于类的对象上
};
int Person::m_B = 10;
void test01()
{
Person p;
//空对象占用内存空间为1
/*C++编译器给每个空对象也分配一个字节的空间,为的是区分空对象在占内存的位置,
没一个空对象也应该有一个独一无二的内存地址*/
cout << sizeof(p) << endl;
}
void test02()
{
Person p;
cout << sizeof(p) << endl;
}
int main(void)
{
test01();
test02();
system("pause");
return 0;
}
this指针
我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
1、当形参和成员变量同名时,可用this指针来区分
2、在类的非静态成员函数中返回对象本身,可使用return *this
#define _crt_secure_no_warnings
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
age = age;
}
int age;
};
void test01()
{
Person p(18);
cout <<"p.age="<< p.age << endl;
}
int main()
{
test01();
}
成员属性的age和上面三个的age不是一回事 因此会输出乱码
#define _crt_secure_no_warnings
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向的是被调函数的成员函数所属的对象
//这里指向的就是p 谁调用的有参构造就调用谁
this->age = age;
}
//返回本体要用应用的方式进行返回
//这里返回值如果是Person,就创建了一个新的对象
Person& PersonAddPerson(Person &p)
{
this->age += p.age;
return *this;
}
int age;//注意起名规范也可以解决名字冲突的问题
};
//解决对象名称冲突
void test()
{
Person p(18);
cout << p.age << endl;
}
//返回对象本身用*this
void test01()
{
Person p1(10);
Person p2(10);
//p2.PersonAddPerson(p1);//将p1和p2的加在一起
//多次追加,return *this;
//链式编程思想
p2.PersonAddPerson(p1).PersonAddPerson(p1);
cout << p2.age << endl;
}
int main(void)
{
test01();
system("pause");
return 0;
}
PersonAddPerson 函数返回一个 Person 类型的引用 Person&。这是因为该函数的目的是修改调用它的对象的成员变量 age,并且希望在修改后能够保留对原始对象的引用。
如果不返回引用,而是直接返回 Person 对象,那么每次调用 PersonAddPerson 函数时都会创建一个新的 Person 对象,这样就无法实现对原始对象的修改。而返回引用可以保证在调用函数后,原始对象的值会被修改,并且可以继续对修改后的对象进行操作
在C++中,当函数返回类型为类类型时,若返回值不是通过引用(即返回类型不是类的引用类型),那么会触发拷贝构造函数或移动构造函数,从而创建一个新的对象。这种行为是基于C++的值语义设计,确保值类型的对象在赋值或返回时都会得到一份独立的拷贝。
#define _crt_secure_no_warnings
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向的是被调函数的成员函数所属的对象
//这里指向的就是p 谁调用的有参构造就调用谁
this->age = age;
}
/*返回本体要用引用的方式进行返回
这里返回值如果是Person,就创建了一个新的对象
Person PersonAddPerson(Person &p)//20 30
这是实际上是以值的方式返回了局部对象
*/
Person& PersonAddPerson(Person &p)//20 40
{
this->age += p.age;
return *this;
}
int age;//注意起名规范也可以解决名字冲突的问题
};
//解决对象冲突
void test()
{
Person p(18);
cout << p.age << endl;
}
//返回对象本身用*this
void test01()
{
Person p1(10);
Person p2(10);
//p2.PersonAddPerson(p1);//将p1和p2的加在一起
cout << p2.age << endl;
//多次追加,return *this;
//链式编程思想
p2.PersonAddPerson(p1).PersonAddPerson(p1);
cout << p2.age << endl;
}
int main(void)
{
test01();
system("pause");
return 0;
}
this指针是用来访问成员变量的
当调用一个成员函数时,编译器会隐式地为该成员函数的参数列表中添加一个指向当前对象的指针,即 this 指针。这是编译器的一种行为,它使得在成员函数内部可以访问对象的成员变量和其他成员函数,因为 this 指针指向调用该成员函数的对象
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << this->mAge << endl;//再属性的前面都默认加了this->
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
//**this指针指向被调用的成员函数所属的对象**
}
int main() {
test01();
system("pause");
return 0;
}
this指针为NULL的情况是因为你将一个空指针 NULL 分配给了指向 Person 对象的指针 p。因此,当你通过空指针调用 ShowPerson() 函数时,this 指针也就成为了 NULL。
Person * p = NULL; // 将空指针赋值给p
p->ShowPerson(); // 在这里调用ShowPerson函数,this指针也就是NULL
在这种情况下,this 指针在 ShowPerson() 函数内部实际上就是指向 NULL 的,因此在 ShowPerson() 函数中对 this 指针的检查会发现它是 NULL,于是函数直接返回了,而没有继续执行下去。
要避免这种情况,你应该确保在调用成员函数时,对象指针指向了一个有效的对象,而不是一个空指针。否则,程序的行为将是不确定的
加入这句话来进行判断:
if (this == NULL) {
return;
}
实际上这只是一个空指针 并没有确切的对象 this指向空的值
const修饰的成员函数
常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数
不用const的简单示例:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person
{
public:
void showPerson()
{
m_A = 100;
}
int m_A;
};
int main()
{
Person p;
p.showPerson(); // 调用函数,不使用返回值
cout << p.m_A << endl; // 打印对象的成员变量
return 0; // 补充:main 函数需要返回一个整数值
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//常函数
class Person
{
public:
//this指针的本质是指针常量,这就规定了指针的指向是不可以修改的
//就相当于Person *const this;
//在成员函数后面加const修饰的是this指针,让指针指向的值也不可以修改
void showPerson() const//加个const就不允许修改了
{
this->m_b = 100;
//this = NULL; //err this指针是不可以修改指针的指向的
}
int m_a;
mutable int m_b;//加了mutable修饰的特殊变量,即使在常函数,常对象中,也可以修改这个值
void func()
{
m_a = 100;//常对象是不能调用普通成员函数的
}
};
void test()
{
Person P;
P.showPerson();
}
//常对象
void test1()
{
const Person p;//在对象前加const,变为常对象
//p.m_a = 100; //err 这个对象的属性也是不允许修改的
p.m_b = 100;//这里的m_b是特殊值在常对象下也是可以修改的
//常对象只能调用常函数
p.showPerson();
cout << p.m_b << endl;
//p.func();// err 常对象不能调用普通成员函数,因为普通成员函数可以修改属性。
}
int main(void)
{
test();
test1();
system("pause");
return 0;
}
友元
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;
}
这里实际上先调用的是构造函数
友元类
#define _CRT_SECURE_NO_WARNINGS
#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;
}
这里先调用Goodgay的构造函数,由于构造函数里面在堆区new了一个对象,又调用了一次Building的构造函数
友元作为成员函数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
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;//err 私有访问不了
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
补充:explicit
main函数里面的Maker m=10;编译器会优化为Maker m=Maker(10);
explicit是为了不让编译器进行优化的
函数高级
宏函数的缺陷
宏函数和内联函数都是在编程中用于提高程序性能的技术,它们都有各自的优缺点。
宏函数是通过预处理器定义的一种宏,它将代码中的特定片段进行简单的文本替换。它的优点是速度快,因为不需要函数调用的开销,而是直接将宏展开到调用的地方。但是,它也存在一些缺点:
类型安全性差:宏没有类型检查,因此可能会导致类型错误,这在大型项目中可能会变得难以调试和维护。
可读性差:宏展开后的代码可能会变得晦涩难懂,因为宏的替换是直接的文本替换,可能会导致代码变得冗长或难以理解。
副作用:宏可能会因为多次展开而导致副作用,比如在宏中传递的参数会被多次计算,可能会导致不可预料的行为。
举例来说,考虑一个简单的宏函数用于计算平方的代码:
#define SQUARE(x) ((x) * (x))
使用这个宏的好处是可以像函数一样调用:
int result = SQUARE(5); // 展开为 (5) * (5),结果为 25
但是,如果传入的是一个有副作用的表达式,可能会导致问题:
int a = 5;
int result = SQUARE(++a); // 展开为 (++a) * (++a),可能导致未定义行为
内联函数是一种将函数调用处用函数体替换的机制,这样可以减少函数调用的开销,并且保留了函数的类型检查。它的优点是:
类型安全:内联函数和普通函数一样具有类型检查,因此可以避免宏的类型错误问题。
可读性:内联函数在调用处是函数体的直接替换,因此代码清晰易读,不会像宏那样引入文本替换可能导致的混乱。
减少函数调用开销:与普通函数相比,内联函数可以避免函数调用的开销,提高程序的性能。
但是,内联函数也有缺点:
代码膨胀:内联函数在每个调用处都会被复制一份函数体,可能会导致代码膨胀,增加程序的大小。
编译时间增加:内联函数的复制可能会增加编译时间,特别是在大型项目中。
举例来说,考虑一个简单的内联函数用于计算平方的代码:
inline int square(int x) {
return x * x;
}
使用内联函数的好处是可以像普通函数一样调用,同时避免了函数调用的开销:
int result = square(5); // 直接展开为 return 5 * 5;
什么情况不会存在内联函数
1.存在过多的条件判断语句
⒉函数体过大
3.对函数进行取址操作
4不能存在任何形式的循环语句
函数的默认参数
- 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
- 如果函数声明有默认值,函数实现的时候就不能有默认参数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//int func(int a=10,int b,int c=10)//err
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
system("pause");
return 0;
}
第一个ret=50是你传入的是20,20。虽然默认参数是10,10,显然是根据你传入的值进行运算,默认参数的好处是可以省略掉传参的步骤
如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值,如下所示:err
函数的声明和实现不能同时有函数的参数(只能有一个):
函数的占位参数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int = 10) {
//void func(int a, int) { //这种写法也是允许的
cout << "this is func" << endl;
}
int main() {
func(10, 10); //占位参数必须填补
system("pause");
return 0;
}
函数重载的基本概念
函数重载的目的:是为了更好地去使用函数名
函数重载的条件:同一个作用域,参数的个数不同,参数的顺序不同,参数的类型不同
c语言中不允许出现相同地函数名,而在c++中是允许出现相同的函数名的 //这也是c语言和c++语言的区别之一
函数重载的作用:函数名可以相同,提高复用性
函数重载满足条件:
1、同一个作用域下
2、函数名称相同
3、函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件(void int)
函数重载和函数的默认参数一起使用,需要注意二义性问题
编译器是通过你调用函数时,传入的参数来判断调用重载的哪个函数,我们调用函数时不需要写返回值,所以返回值不能成为函数重载的条件
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//参数的个数不同
void func()
{
cout << "func()" << endl;
}
void func(int a)
{
cout << "func(int a)" << endl;
}
//参数的类型不同
void func(char c)
{
cout << "func(char c)" << endl;
}
//参数的顺序不同
void func(int a, double b)
{
cout << "func(int a, double b)" << endl;
}
void func(double b, int a)
{
cout << "func(double b, int a)" << endl;
}
void test01()
{
int a = 10;
double b = 3.14;
//编译器是通过你调用的函数时,传入的参数来判断调用重载的哪个函数
func();
//func(b);//err double转换不了成为int或char
func(a, b);
func(b, a);
char c = 'c';
func(c);// char可以转换int,调用int参数的函数
}
//--------------------------------------------------------
//函数重载和函数的默认参数一起使用
void myfunc(int a, int b = 0)
{
cout << "myfunc(int a, int b = 0)" << endl;
}
void myfunc(int a)
{
cout << "myfunc(int a)" << endl;
}
void test02()
{
//myfunc(10);//err 二义性 不知道调用哪个函数
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
注意:const int 和int也是属于类型不同
const int a=20;
//int *p=&a;//err
int *p=(int*)&a;
//函数重载注意事项
//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); //碰到默认参数产生二义性,需要避免 err 编译器会认为都能调用
system("pause");
return 0;
}
函数重载的原理
函数重载的原理是在汇编时,给各个函数取别名,C语言不能重载的原因是没有取别名
C++的函数在汇编时,会给函数取别名,C语言的不会,这时,如果C++来调用C语言的函数,C++会去找取了别名的函数,但是C语言没有取别名,这时会出错
c++调用c语言函数
test.h
#pragma once
#include<stdio.h>
//告诉c++编译器,找下面的函数要以c语言方式去寻找
#ifdef __cplusplus
extern "C"
{
#endif
void func();
#ifdef __cplusplus
}
#endif
test.c
#include "test.h"
void func()
{
printf("func\n");
}
22 c++调用c语言函数.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include"test.h"
int main()
{
func();
system("pause");
return EXIT_SUCCESS;
}
运算符重载
运算符重载的概念
1.运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
2.运算符重载的目的是让语法更加简洁
3.运算符重载不能改变本来寓意,不能改变基础类型寓意
4.运算符重载的本质是另一种函数调用(是编译器去调用)
5.这个函数同一的名字叫operator
6.重载函数可以写成全局或成员函数
7.重载函数如果写成全局的,那么双目运算符左边的是第一个参数,右边是第二个参数
8.重载函数如果写成成员函数,那么双目运算符的左边是this,右边是第一个参数
9.不能改变运算符优先级,不能改变运算符的参数个数
短路规则是:与运算前面假就假后面无需判断,或者是或运算前面真就真后面无需判断
加号运算符重载
this指针指向调用这个函数的对象(不是p也不是temp,而是.PersonAddPerson()前面的那个东西)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public:
Person() {};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;//这里的this指针指向的是p2
temp.m_B = this->m_B + p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式
Person p3 = p2 + p1; //相当于 p2.operaor+(p1)
/*
this指针指向的是p2 返回对象之后 又调用了一次拷贝构造函数 这里我没写 编译器会自动生成
*/
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main() {
test();
system("pause");
return 0;
}
总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
总结2:不要滥用运算符重载
减号运算符重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Maker
{
public:
Maker(int id)
{
this->id = id;
}
public:
int id;
//写成成员函数,那么只需要一个参数,这个参数是减号的右边
Maker operator-(Maker &m2)
{
Maker tmp(this->id - m2.id);
return tmp;
}
};
int operator-(Maker &m, int b)
{
return m.id - b;
}
void test()
{
Maker m1(10);
Maker m2(5);
Maker m3 = m1 - m2;
cout << m3.id << endl;
int a = m3 - 5;
cout << a << endl;
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
左移运算符重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person {
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}
private:
int m_A;
int m_B;
};
//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << " hello world" << endl; //链式编程
}
int main() {
test();
system("pause");
return 0;
}
cout<<p1相当于调用了<<的重载 返回了cout的对象 而out是我给cout取的别名
总结:重载左移运算符配合友元可以实现输出自定义数据类型
在重载左移操作符(operator<<)时,通常需要访问类的私有成员来完成操作。但是,C++的访问控制规则限制了非成员函数对私有成员的访问。通过使用友元函数,我们可以解除这种限制,使得特定的函数能够访问类的私有成员,这样就可以实现对该类的输出操作
右移运算符重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
void test()
{
int a;
cin >> a;
cout << a << endl;
}
class Maker
{
friend istream &operator>>(istream &in, Maker &m);
public:
Maker(string name, int age)
{
this->name = name;
this->age = age;
}
int getAge()
{
return age;
}
private:
string name;
int age;
};
istream &operator>>(istream &in, Maker &m)
{
in >> m.age;
in >> m.name;
return in;
}
void test02()
{
Maker m("悟空", 15);
Maker m2("悟空", 15);
cin >> m >> m2;
cout << m.getAge() << endl;
cout << m2.getAge() << endl;
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
赋值运算符重载
编译器默认给类提供了一个默认的赋值运算符重载函数
默认的赋值运算符重载函数进行了简单的赋值操作
当类有成员指针时,然后在构造函数中申请堆区空间,在析构函数中释放堆区空间
c++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
//在这里数据已经开辟到了堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person &p)
{
//如果p2在堆区是有空间的 应该先释放 再进行深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
在 C++ 中,赋值运算符 = 是右结合的,这意味着表达式 p3 = p2 = p1 会先执行 p2 = p1,然后将结果赋给 p3。
在这种情况下,p2 = p1 返回的是 p2 的引用。因此,p3 会被赋值为 p2。
这段代码中的 if (m_Age != NULL) 语句用于检查指针 m_Age 是否为空。在 C++ 中,对空指针进行 delete 操作是安全的,但如果尝试删除已经被删除过的指针或者空指针会导致未定义的行为。因此,在 Person 类的析构函数和赋值运算符重载函数中,首先检查 m_Age 是否为空,如果不为空,则释放该指针所指向的内存空间,并将指针置为空指针(nullptr 或 NULL)。
这样做的目的是确保在释放内存之前先进行有效性检查,以避免悬空指针或重复释放已释放的内存空间,从而提高程序的健壮性和稳定性
关系运算符重载
**作用:**重载关系运算符,可以让两个自定义类型对象进行对比操作
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
递增运算符重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
在函数执行完毕后,包含temp的栈帧会被销毁,这时temp对象也会被销毁。所以,temp对象的生命周期仅限于operator++(int)函数的执行期间。因此,如果你尝试返回temp的引用,这个引用将指向一个已被销毁的对象,这是未定义行为,可能导致程序崩溃或产生不可预测的结果
加了一个 int 类型的占位符参数的函数被认为是后置递增运算符,因为后置版本需要返回旧值,然后再执行递增操作。
前置加加和后置加加
/你的第二个operator++返回了一个临时变量,这个在C++里面是一个右值(简单来说就是只能放在 = 号右边)
而你重载的 << 操作符号第二个参数是一个非const的引用,非const的引用需要一个左值(可以放在 = 号左边,可以赋值)来初始化,因此报错
解决办法很简单,把你重载的 << 符号第二个参数加上const修饰即可。加了const修饰的引用可以用右值来初始化/
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
void test()
{
int a = 1;
cout << ++a << endl;
cout << a++ << endl;
cout << a << endl;
++(++a);
}
class Maker
{
friend ostream &operator<<(ostream &out, Maker const &m);
public:
Maker(int a)
{
this->a = a;
//cout << "1" << endl;
}
//重载前置++
Maker &operator++()
{
++this->a;
return *this;
}
//重载后置++
//这里不用引用 是因为
//如果返回引用这里是返回局部的引用 在这个函数执行完时,被释放掉了
Maker operator++(int)//区分后置++ //占位参数,必须是int
{
//后置++,先返回,后++
Maker tmp(*this); //1、*this里面的值a是等于2的
++this->a; //这个对象的a是等于3的
return tmp;//返回的是右值常量 零时变量 调用一次构造函数
/*你的第二个operator++返回了一个临时变量,这个在C++里面是一个右值(简单来说就是只能放在 = 号右边)
而你重载的 << 操作符号第二个参数是一个非const的引用,非const的引用需要一个左值(可以放在 = 号左边,可以赋值)来初始化,因此报错
解决办法很简单,把你重载的 << 符号第二个参数加上const修饰即可。加了const修饰的引用可以用右值来初始化*/
/* cout << "后置加加" << endl;
return Maker(10);*/
}
private:
int a;
};
ostream &operator<<(ostream &out, Maker const &m)
{
out << m.a << endl;
return out;
}
void test01()
{
Maker m1(1);
//cout << m1 << endl;//1
//cout << ++m1 << endl;//2
cout << (m1++) << endl;//2
//cout << m1 << endl;//3
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
函数调用运算符重载
函数调用运算符 () 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void MyPrint02(string test)
{
cout << test << endl;
}
void test01()
{
MyPrint myPrint;
myPrint("hello world");//由于使用起来类似于函数调用,因此称为仿函数
MyPrint02("hello world");
}
//仿函数非常灵活,没有固定写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100, 100);
cout << "ret=" << ret << endl;
//匿名函数对象 仿函数 MyAdd()匿名对象
//类型+()就是匿名对象的语法
cout << MyAdd()(100, 100) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
数组下标重载(回头看)
MyArray.h
#pragma once
#include<iostream>
using namespace std;
class MyArray
{
public:
MyArray();
//拷贝构造
MyArray(const MyArray &arr);
MyArray(int capacity, int val = 0);
//重写赋值运算符重载函数
MyArray &operator=(const MyArray &m);
//要能当左右值
int &operator[](int index);
~MyArray();
//头插
void PushFront(int val);
//尾插
void PushBack(int val);
//头删
void PopFront();
//尾删
void PopBack();
//获取数组元素个数
int Size();
//获取数组容量
int Capacity();
//指定位置插入元素
void Insert(int pos, int val);
//获取指定位置的值
int &Get(int pos);//返回引用可以当左值 又可以当右值
//在指定位置修改值
void Set(int pos, int val);
private:
int *pArray;//指向堆区空间,存储数据
int mSize;//元素个数
int mCapacity;//容量
};
MyArray.cpp
#include "MyArray.h"
MyArray::MyArray()
{
this->mCapacity = 20;
this->mSize = 0;
this->pArray = new int[this->mCapacity];
//初始化
for (int i = 0; i < this->mCapacity; i++)
{
this->pArray[i] = 0;
}
}
//深拷贝
MyArray::MyArray(const MyArray &arr)
{
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申请空间
this->pArray = new int[arr.mCapacity];
//拷贝数据
for (int i = 0; i < this->mSize; i++)
{
this->pArray[i] = arr.pArray[i];
}
}
//MyArray::MyArray(int capacity, int val = 0)//err
//声明和实现只能有一个默认参数
MyArray::MyArray(int capacity, int val)
{
this->mCapacity = capacity;
this->mSize = capacity;
this->pArray = new int[capacity];
for (int i = 0; i < this->mSize; i++)
{
this->pArray[i] = val;
}
}
MyArray::~MyArray()
{
if (this->pArray != NULL)
{
delete[] this->pArray;
this->pArray = NULL;
}
}
//头插
void MyArray::PushFront(int val)
{
//判断容量是否满
if (this->mSize == this->mCapacity)
{
return;
}
for (int i = this->mSize - 1; i >= 0; i--)
{
this->pArray[i + 1] = this->pArray[i];
}
//空出了0的位置
this->pArray[0] = val;
//维护元素个数
this->mSize++;
}
//尾插
void MyArray::PushBack(int val)
{
//判断容量是否满
if (this->mSize == this->mCapacity)
{
return;
}
this->pArray[this->mSize] = val;
this->mSize++;
}
//头删 后面的数往前移动来覆盖第一个元素
void MyArray::PopFront()
{
if (this->mSize == 0)
{
return;
}
for (int i = 0; i < this->mSize - 1; i++)
{
this->pArray[i] = this->pArray[i + 1];
}
this->mSize--;
}
//尾删
void MyArray::PopBack()
{
if (this->mSize == 0)
{
return;
}
this->mSize--;
}
//获取数组元素个数
int MyArray::Size()
{
return this->mSize;
}
//获取数组容量
int MyArray::Capacity()
{
return this->mCapacity;
}
//指定位置插入元素
void MyArray::Insert(int pos, int val)
{
//判断容量是否满
if (this->mSize == this->mCapacity)
{
return;
}
//如果位置不合法,就插入到尾部
if (pos<0 || pos>this->mSize - 1)
{
pos = this->mSize;
}
for (int i = this->mSize - 1; i >= pos; i--)
{
this->pArray[i + 1] = this->pArray[i];
}
//pos的位置空出
this->pArray[pos] = val;
this->mSize++;
}
//获取指定位置的值 //返回引用可以当左值 又可以当右值
int &MyArray::Get(int pos)
{
return this->pArray[pos];
}
//在指定位置修改值
void MyArray::Set(int pos, int val)
{
if (pos < 0 || pos > this->mCapacity - 1)
{
return;
}
this->pArray[pos] = val;
}
//重写赋值运算符重载函数
MyArray &MyArray::operator=(const MyArray &m)
{
cout << "赋值函数" << endl;
if (this->pArray != NULL)
{
delete[] this->pArray;
this->pArray = NULL;
}
this->mCapacity = m.mCapacity;
this->mSize = m.mSize;
//2、申请堆区空间,大小由stu决定
this->pArray = new int[m.mCapacity];
//3、拷贝数据
//for (int i = 0; i < this->mSize; i++)//err
cout << "this->mSize=" << this->mSize << endl;
for (int i = 0; i < this->mCapacity; i++)
{
this->pArray[i] = m.pArray[i];
}
//4、返回对象本身
return *this;
}
//要能当左右值
int &MyArray::operator[](int index)
{
if (this->mSize <= index)
{
this->mSize++;
}
return this->pArray[index];
}
62 数组下标运算符重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include"MyArray.h"
void test01()
{
MyArray arr;
for (int i = 0; i < 20; i++)
{
arr[i] = i + 10;
}
for (int i = 0; i < 20; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void test02()
{
MyArray arr;
for (int i = 0; i < 20; i++)
{
arr[i] = i + 10;
}
for (int i = 0; i < 20; i++)
{
cout << arr[i] << " ";
}
MyArray arr2;
arr2 = arr;
for (int i = 0; i < 20; i++)
{
cout << arr2[i] << " ";
}
cout << endl;
cout << arr2.Size() << endl;
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
继承
继承
继承的好处:可以减少重复的代码
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。普通的输出
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//普通实现页面
//java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footet()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,c++...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科的视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footet()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,c++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科的视频" << endl;
}
};
class Cpp
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footet()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,c++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科的视频" << endl;
}
};
void test01()
{
cout << "Java的下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footet();
ja.left();
ja.content();
cout << "--------------------" << endl;
Python py;
py.header();
py.footet();
py.left();
py.content();
cout << "--------------------" << endl;
Cpp cpp;
cpp.header();
cpp.footet();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
继承的案例
继承的好处
减少重复的代码
**继承的语法:class 子类 : 继承方式 父类 **
子类也称为派生类 父类也称为派生类
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//继承实现页面
//公共页面类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footet()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,c++...(公共分类列表)" << endl;
}
};
class Java : public BasePage
{
public:
void content()
{
cout << "Java学科的视频" << endl;
}
};
class Python : public BasePage
{
public:
void content()
{
cout << "Python学科的视频" << endl;
}
};
class Cpp : public BasePage
{
public:
void content()
{
cout << "Cpp学科的视频" << endl;
}
};
void test01()
{
cout << "Java的下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footet();
ja.left();
ja.content();
cout << "--------------------" << endl;
Python py;
py.header();
py.footet();
py.left();
py.content();
cout << "--------------------" << endl;
Cpp cpp;
cpp.header();
cpp.footet();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
继承的三种方式方式:
公有继承
私有继承
保护继承
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//继承方式
//公有继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base1
{
public:
void func()
{
m_A = 10; // 父类中的公共权限成员到子类中依然是公共权限
m_B = 10; // 父类中的保护权限成员到子类中依然是保护权限
//m_C = 10; err // 父类中的私有权限成员到子类中访问不到
}
};
void test01()
{
Son1 s1;
//公共权限类内可以访问 类外也可以访问
s1.m_A = 100;
//到了子类中 是保护权限
//类内可以访问 类外不可以访问
//s1.m_B = 100; err
}
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//保护继承
class Son2 :protected Base2
{
public:
void func()
{
m_A = 10; // 父类中的公共权限成员到子类中是保护权限
m_B = 10; // 父类中的保护权限成员到子类中依然是保护权限
//m_C = 10; err // 父类中的私有权限成员到子类中访问不到
}
};
void test02()
{
Son2 s1;
//s1.m_A = 1000;//err
//s1.m_B = 1000;//err
}
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base3
{
public:
void func()
{
m_A = 10; // 父类中的公共权限成员到子类中是私有权限
m_B = 10; // 父类中的保护权限成员到子类中是私有权限
//m_C = 10; err // 父类中的私有权限成员到子类中访问不到
}
};
void test03()
{
Son2 s1;
//s1.m_A = 1000;//err
//s1.m_B = 1000;//err
}
class GrandSon3 :public Son3
{
public:
void func()
{
//到了Son3中,即使是儿子也是访问不到的
//m_A = 1000;//err
//m_B = 1000;//err
}
};
int main()
{
//test01();
//test02();
test03();
system("pause");
return EXIT_SUCCESS;
}
继承中的对象类型
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
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()
{
//16
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器隐藏了 是访问不到的 但是确实是继承下去了
cout << "size of Son =" << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
可以利用这个工具查看对象模型
利用开发人员命令提示工具查看对象模型
跳转文件路径cd 具体路径下
查看命名
cl /d1 reportSingleclassLayout类名 文件名
1、2、
就能看到这个继承的对象模型
m_A m_B m_ C是从父类继承下来的
m_D是子类特有的
结论:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
继承中的构造和析构顺序
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//继承中的构造和析构的顺序
class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}
~Base()
{
cout << "Base析构函数!" << endl;
}
};
class Son :public Base
{
public:
Son()
{
cout << "Son构造函数!" << endl;
}
~Son()
{
cout << "Son析构函数!" << endl;
}
};
void test01()
{
//Base b;
//cout << "------------------" << endl;
//继承中的构造和析构的顺序如下:
//先构造父类,再构造子类,析构的顺序与构造的顺序相反
Son s;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//继承中的构造和析构的顺序
class abc
{
public:
abc()
{
cout << "abc构造函数!" << endl;
}
~abc()
{
cout << "abc析构函数!" << endl;
}
};
class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}
~Base()
{
cout << "Base析构函数!" << endl;
}
};
class Son :public Base
{
private:
abc men;
public:
Son()
{
cout << "Son构造函数!" << endl;
}
~Son()
{
cout << "Son析构函数!" << endl;
}
};
void test01()
{
Son s;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
Son 类中有一个名为 men 的 abc 类对象作为成员变量。因此,当你创建一个 Son 类的对象时,会自动调用 Son 类的构造函数。在 Son 类的构造函数中,men 对象也会被创建,这就触发了 abc 类的构造函数
继承中同名成员的处理
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
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;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son-func调用" << endl;
}
int m_A;
};
//同名的成员属性的处理方式
void test01()
{
Son s;
cout << "Son 下 m_A=" << s.m_A << endl;
//如果通过子类对象 访问到父类中的同名成员,需要加作用域
cout << "Base 下 m_A=" << s.Base::m_A << endl;
}
//同名成员函数处理方式
void test02()
{
Son s;
s.func();//直接调用 调用的是子类中的同名成员
//如何调用到父类中同名成员函数
s.Base::func();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//s.func(100);err
//访问子类的同名成员隐藏掉父类中所有同名成员函数,需要加作用域
s.Base::func(100);//调用函数重载
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
1.子类对象可以直接访问到子类中同名成员
2.子类对象加作用域可以访问到父类同名成员
3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
同名静态成员处理
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//继承中的同名静态成员处理方式
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base - static void()的调用" << endl;
}
static void func(int a)
{
cout << "Base - static void(int)的调用" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void()的调用" << endl;
}
};
int Son::m_A = 200;
//同名静态成员属性
void test01()
{
//1、通过对象来访问数据
cout << "通过对象访问:" << endl;
Son s;
cout << "Son 下 m_A=" << s.m_A << endl;
cout << "Base 下 m_A=" << s.Base::m_A << endl;
//2、通过类名来访问数据
cout << "通过类名来访问:" << endl;
cout << "Son 下 m_A=" << Son::m_A << endl;
//第一个::代表通过类名方式访问
//第二个::代表访问父类作用域下
cout << "Base 下 m_A=" << Son::Base::m_A << endl;
}
//同名静态成员函数
void test02()
{
//1、通过对象访问
cout << "通过对象访问:" << endl;
Son s;
s.func();
s.Base::func();
//2、通过类名访问
cout << "通过类名来访问:" << endl;
Son::func();
Son::Base::func();
//子类出现了和父类同名的静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏的同名函数,要加作用域
Son::Base::func(100);
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
多继承语法
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//多继承语法
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//子类 需要继承Base1和Base2
//语法:class 子类:继承方式 父类1,继承方式 父类2 ...
class Son :public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01()
{
Son s;
cout << "sizeof Son " << sizeof(s) << endl;
//当父类中出现了同名成员,需要加作用域区分 两个父类有同一个成员变量时候需要加作用域
//不然有二义性
cout << "m_A" << s.Base1::m_A << endl;
cout << "m_A" << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
菱形继承
菱形继承的问题:
会导致数据有两份,导致资源的浪费
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//动物类
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 = 18;
st.Tuo::m_Age = 28;//覆盖掉了
//当菱形继承的时候,两个父类拥有相同的数据,需要加以作用域去区分
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;
cout<<sizeof(SheepTuo)<<endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
虚继承的底层逻辑
vbptr是虚基类指针
32位系统指针占了4个字节
这里是通过偏移量来寻找基类
Sheep 和 Tuo 类各继承了一个虚基类 Animal,因此在 SheepTuo 类中会有一个指向 Animal 的虚基类指针。
Sheep 类本身没有其他成员变量,但由于虚继承,会多出一个指针大小(通常是4字节或8字节,取决于编译器和操作系统)。
Tuo 类本身没有其他成员变量,同样会多出一个指针大小。
SheepTuo 类并没有自己的成员变量。
所以,sizeof(SheepTuo) 等于两个指针大小的总和加上 Animal 类的大小。假设指针大小为4字节,Animal 类有一个 int 类型的成员变量 m_Age,因此 sizeof(Animal) 应该是4字节。所以:
sizeof(SheepTuo) = 2 * sizeof(vbptr) + sizeof(Animal)
= 2 * 4 + 4
= 8 + 4
= 12
所以,结果是 12 字节。
案例2:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Base {
public:
Base(int x) { cout << "Base constructor: " << x << endl; }
};
class Base1 : public virtual Base {
public:
Base1(int x) : Base(x) { cout << "Base1 constructor: " << x << endl; }
};
class Base2 : public virtual Base {
public:
Base2(int x) : Base(x) { cout << "Base2 constructor: " << x << endl; }
};
class Derived : public Base1, public Base2 {
public:
Derived(int x, int y) : Base(3 * x), Base1(x), Base2(y) {
cout << "Derived constructor" << endl;
}
};
int main()
{
Derived obj(10, 20);
return 0;
}
构造函数的调用顺序是根据继承关系来确定的,而不是初始化列表中的顺序
多态是C++面向对象三大特性之一
多态分为两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类(子类)和虚函数实现运行时多态
静态多态和动态多态区别:
静态多态的函数地址早绑定–编译阶段确定函数地址·动态多态的函数地址晚绑定–运行阶段确定函数地址
C++中的多态性通常通过虚函数来实现。虚函数是在基类中声明的,派生类可以重写该函数并提供自己的实现。当通过基类指针或引用调用虚函数时,实际调用的是派生类的实现。这种机制允许在运行时动态地确定要调用哪个函数,从而实现多态性。如果没有虚函数,就无法实现多态性。
多态
多态的语法
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
void DoSpeak(Animal & animal)
{
animal.speak();
}
void test01()
{
Cat cat;
DoSpeak(cat);
}
int main() {
test01();
system("pause");
return 0;
}
我们可以看到执行说话的函数 C++中父类的引用能够直接指向子类对象 这是属于地址早绑定 在编译阶段确定了函数地址 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定 也就是地址晚绑定
下面是修改后的代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
//虚函数
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 &animal=cat
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
动态多态的满足条件
1、有继承关系
2、子类要重写父类的虚函数
重载和重写的区别
1、重载:函数的名字相同,参数不同
2、重写:函数的返回值类型,函数名要相同,参数列表要完全相同
子类重写父类的虚函数的时候 父类要加virtual 子类可加可不加virtual
动态多态的使用
父类的指针或者引用 指向子类对象 由父类的指针或者对象进行调用
多态的底层原理图
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
void test02()
{
cout << "sizeof Animal=" << sizeof(Animal) << endl;
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
void test02()
{
cout << "sizeof Animal=" << sizeof(Animal) << endl;
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
在这个示例中,sizeof(Animal) 打印出来的结果是 4,这是因为 C++ 中的虚函数表(vtable)机制导致的。在 Animal 类中,虽然只有一个虚函数 speak(),但是对于每个包含虚函数的类,编译器都会在对象的内存布局中添加一个指向虚函数表的指针(vptr)。
因此,即使 Animal 类中只有一个虚函数,它也会有一个指针指向虚函数表,这会增加对象的大小。在大多数情况下,指针的大小是 4 个字节(32 位系统)或 8 个字节(64 位系统),因此 sizeof(Animal) 的结果是 4 或 8,取决于系统的位数
动态的多态
选函数表的指针指向了虚函数表
子类继承父类的结构图
子类继承父类重写虚函数后的结构图
写了class Animal
{
public:
//虚函数
virtual void speak()
{
cout << “动物在说话” << endl;
}
};
之后
animal里面会有一个四个字节大小的指针,这个指针就是vfptr,虚函数表记录着虚函数的地址
虚函数表(vtable)是一个指针数组,其中存储了指向虚函数的地址。每个类都有一个虚函数表,包含该类及其所有基类的虚函数的地址。
当一个类中包含虚函数时,编译器会在其对象中插入一个指向虚函数表的指针(通常称为虚函数表指针或 vptr)。这个指针指向该类的虚函数表,通过这个表可以找到虚函数的实际实现地址
子类不重写的结构原理图
静态成员函数和非静态成员函数都是存放在代码区的。
是类可以直接调用静态成员函数。不可以直接调用非静态成员函数
因此这里的Size(1)
子类重写后的结构原理图
多态案1——计算机类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
代码组织结构清晰 可读性强
利于前期和后期的扩展以及维护
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
//分别利用普通写法和多态技术实现计算机类
//普通写法
class Calculator
{
public:
int getResulr(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
//如果想要拓展新的功能,需要修改源码
//在真实的开发中,开闭原则
//开闭原则:对扩展进行开放,对修改进行关闭
}
int m_Num1;
int m_Num2;
};
void test01()
{
//创建计算机对象
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
//cout << c.getResulr("+") << endl;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " <<c.getResulr("+")<< endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResulr("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResulr("*") << endl;
}
//利用多态来实现计算器
//多态的好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期的拓展和维护性高
//实现计算机抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多态的使用条件 这里用的是指针
AbstractCalculator *abc = new AbstractCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完后销毁数据
delete abc;//释放的是堆区的数据 但指针的类型没有变化
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
//乘法运算
abc = new MulCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main()
{
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
假设你要让这个计算器有其它的运算方法传统的写法是不提倡了,这个架构不利于维护,因此这就是在这里引入多态的原因
纯虚函数和抽象类
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//纯虚函数和抽象类
//只要有一个纯虚函数,这个类称为抽象类
class Base
{
public:
virtual void func() = 0;
};
class Son :public Base
{
public:
//子类必须重写父类中的纯虚函数,否则无法实例化
virtual void func() {};
};
void test01()
{
//1、无法实例化对象
//Base b; err
//new Base; err
//2、子类中要重写父类中的纯虚函数
Son s;
//一般调用
//s.func();
//利用多态的技术进行调用
Base *base=new Son;
base->func();
}
int main()
{
//test01();
system("pause");
return EXIT_SUCCESS;
}
多态案例2——饮品
案例描述:
制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//多态案例2 制作饮品
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
//下面这些函数实现的时候都是从子类中去寻找实现
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee :public AbstractDrinking
{
//煮水
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入精液" << endl;
}
};
//制作茶叶
class Tea :public AbstractDrinking
{
//煮水
virtual void Boil()
{
cout << "煮矿泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入玻璃杯" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入尿液" << endl;
}
};
//制作函数
void doWork(AbstractDrinking * abs)
{
abs->makeDrink();
delete abs;
}
int main()
{
//制作咖啡
doWork(new Coffee);
cout << "----------------------------" << endl;;
//制作茶叶
doWork(new Tea);
system("pause");
return EXIT_SUCCESS;
}
一个接口有多种形态 传入的参数不同会调用不同的函数 这就是多态的好处 只需要在后面进行拓展代码就行了
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
下面案例是在子类中有属性开辟到了堆区,因此走子类中的析构代码,如果是多态是走不到的,因此需要加入虚析构或者是纯虚析构,而纯虚析构要有具体的函数实现,不然不能示例化对象,而虚析构是可以实例化对象的,
初始化:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数调用" << endl;
}
~Animal()
{
cout << "Animal析构函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
};
//猫类
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat调用了构造函数" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
string *m_Name;
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
显然这不是我们所想要的输出结果
这里的问题就是我们用的是父类的指针指向子类的对象 所以当delete父类指针的时候并不会走子类的析构代码 如果子类中有存放在堆区的数据 这样的话会造成内存的泄漏 为了解决这个问题我们只需要把析构改为虚析构即可
虚析构使用避免上面的问题:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数调用" << endl;
}
virtual~Animal()
{
cout << "Animal析构函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
};
//猫类
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat调用了构造函数" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
string *m_Name;
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
纯虚析构的实现:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数调用" << endl;
}
virtual~Animal() = 0;
//纯虚函数
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "Animal的纯虚析构调用" << endl;
}
//猫类
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat调用了构造函数" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
string *m_Name;
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
这个案例中在子类的堆区存储着数据 必须要走子类中的析构 如果单单用多态是走不到的
在使用虚析构函数时,如果父类的析构函数被声明为虚函数(使用virtual关键字),并且派生类(子类)的析构函数也被声明为虚函数,那么在销毁派生类的对象时,父类和子类的析构函数都会被调用
当你有一个纯虚析构函数时,你必须提供其具体实现。为什么呢?
确保正确的析构行为: 如果派生类的析构函数被调用,但基类的析构函数没有具体实现,编译器就会报错。因为派生类销毁时,它的基类部分也需要被销毁,但编译器却不知道该如何处理基类的部分。因此,提供纯虚析构函数的具体实现可以确保正确的析构行为。编译器要求: C++语言规范要求如果类中存在纯虚析构函数,那么该纯虚析构函数也需要有具体的实现。这是为了确保语言的一致性和规范性。
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
多态案例3—— 电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<iostream>
using namespace std;
#include<string>
//抽象不同零件类
//抽象计算函数
class CPU
{
public:
virtual void calculate() = 0;
};
//抽象显示函数
class VideoCard
{
public:
virtual void display() = 0;
};
//抽象存储函数
class Memory
{
public:
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
//构造函数中传入三个零件指针
Computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
m_cpu = cpu;//指针接收
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void work()
{
//调用每个零件工作的接口
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数,释放 3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU * m_cpu;//CPU的零件指针
VideoCard * m_vc;//显卡零件指针
Memory * m_mem;//内存零件指针
};
//具体厂商
//Intel厂商
class IntelCpu :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的VideoCard开始显示了!" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的Memory开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCpu :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的VideoCard开始显示了!" << endl;
}
};
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的Memory开始存储了!" << endl;
}
};
void test01()
{
CPU * intelCpu = new IntelCpu;//父类指向子类对象,//需释放
VideoCard * intelVCard = new IntelVideoCard;//需释放
Memory * intelMem = new IntelMemory;//需释放,
//1.直接delete 在 delete computer1; 后
//2.提供析构函数在 class Computer 中
//创建第一台电脑
Computer * computer1 = new Computer(intelCpu, intelVCard, intelMem);//需释放
computer1->work();
delete computer1;
cout << "-----------------------------" << endl;
cout << "第二台电脑开始工作" << endl;
//创建第二台电脑
Computer * computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);//需释放
computer2->work();
delete computer2;
cout << "-----------------------------" << endl;
cout << "第三台电脑开始工作" << endl;
//创建第三台电脑
Computer * computer3 = new Computer(new IntelCpu, new IntelVideoCard, new IntelMemory);//需释放
computer3->work();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}
这里有的小朋友被指针绕晕了 可以看看下面简单案例 你就懂了
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int *p = &a;
int *q;
q = p;
cout << *q << endl;
}
delete 只会释放对象占用的内存,而不会改变指针本身的值。在 delete 后,指针仍然指向之前分配的内存地址,这是一个悬空指针(dangling pointer)。
为了避免悬空指针的问题,一种常见的做法是在释放内存后将指针设置为 nullptr,这样可以防止意外地使用悬空指针。例如:
delete computer1;
computer1 = nullptr; // 将指针设置为空指针
这样做不仅可以使代码更加安全,还可以更容易地检测和避免悬空指针的问题。
补充
在类里构造函数赋初值后 在类里的下一个函数用到了构造函数里面赋值后的变量 构造函数执行完 变量被释放了吗
在C++中,类的成员变量的生命周期取决于它们的存储类别和在类中的声明位置。如果你在构造函数中为类的成员变量赋初值,并且后续的函数在构造函数执行完毕后使用这些变量,那么这些变量在整个对象的生命周期内都是有效的,而不会在构造函数执行完毕后被释放。
例如:
#include <iostream>
class MyClass {
public:
MyClass(int a, int b) {
m_A = a;
m_B = b;
std::cout << "Constructor executed" << std::endl;
}
void printValues() {
std::cout << "Values: m_A = " << m_A << ", m_B = " << m_B << std::endl;
}
private:
int m_A;
int m_B;
};
int main() {
MyClass obj(10, 20);
obj.printValues();
return 0;
}
在这个例子中,构造函数MyClass(int a, int b)中为m_A和m_B赋初值,而后续的printValues()函数在构造函数执行完毕后使用这两个变量。在对象的整个生命周期内,m_A和m_B都是有效的,它们的生命周期与对象相同。
总的来说,构造函数中赋初值的成员变量在整个对象生命周期内都是有效的,不会在构造函数执行完毕后被释放。
在C++中,对象的生命周期从对象的创建一直到其销毁为止。这包括对象的构造(通过构造函数初始化)和析构(通过析构函数进行清理)阶段。对象的生命周期与其所属的作用域密切相关。
在上述例子中,对象的生命周期始于 MyClass obj(10, 20); 这一行。从这一点开始,对象被创建,构造函数被调用,其中的成员变量 m_A 和 m_B 被初始化。整个对象的生命周期包括构造函数执行、对象存在于作用域内、以及对象被销毁的过程。
在这个生命周期内,构造函数中赋初值的成员变量(m_A 和 m_B)在整个对象的范围内是有效的。它们的生命周期确切地与对象的生命周期相匹配,而不仅仅是在构造函数内部有效。这意味着在对象的其他成员函数中,这些变量仍然是可访问和可用的,直到对象被销毁。