空指针:int *p = NULL;
- 空指针:指针变量指向内存中编号为0的空间;
- 用途:初始化指针变量
- 注意:空指针指向的内存不允许访问
- 注意:内存编号为0-255为系统占用空间,不允许用户访问
野指针:指针变量指向非法的内存空间
总结:空指针和野指针都不是我们申请的空间,因此不要访问
- const修饰指针 -- 常量指针 const int *p = &a;
特点:指针的指向可以修改,但是指针指向的值不能修改。
- const修饰常量 -- 指针常量 int * const p = &a;
特点:指针的指向不可以改,指针指向的值可以改
- count既修饰指针,也修饰常量
特点:指针的指向和指针指向的值都不可以改
#include <bits/stdc++.h>(万能头文件)
冒泡排序:
/* 冒泡排序 */
void bubbleSort(int nums[], int size) {
// 外循环:未排序区间为 [0, i]
for (int i = size - 1; i > 0; i--) {
// 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
C语言动态内存分配(C++ string本质 结构体+字符串)
#include <stdio.h>
#include <stdlib.h> // 用于动态分配内存
#include <string.h>
struct Student {
int id; // 学号
char *name; // 字符指针,用于动态分配内存存储字符串
};
int main()
{
struct Student student1;
// 给成员赋值
student1.id = 1;
// 动态分配内存
student1.name = (char *)malloc(50 * sizeof(char)); // 分配 50 字节
if (student1.name == NULL) {
printf("内存分配失败\n");
return -1;
}
strcpy(student1.name, "李四"); // 将字符串复制到动态分配的内存中
// 打印结构体内容
printf("学号: %d\n", student1.id);
printf("姓名: %s\n", student1.name);
// 释放内存
free(student1.name);
return 0;
}
C++结构体+字符串(string):
#include <iostream>
#include <string>
using namespace std;
struct student
{
string name;
int age;
int score;
};
int main()
{
student s;
s.age = 20;
s.name = "LYF";
s.score = 100;
cout << "姓名:" << s.name << " 年龄:" << s.age << " 分数:" << s.score << endl;
system("pause");
return 0;
}
变量值(数据多)传递多用指针(将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来,注意形参加上const,防止在函数中修改原变量)
Switch:
- 在 switch 的每个 case 块中,可以直接写多行代码,无需大括号 {}。
- 尽管在 case 中 不需要强制使用大括号,但在一些复杂的场景下,加上大括号可以提高代码的可读性或避免逻辑错误。
- 在 case 分支中如果需要定义局部变量,必须用大括号将分支语句包裹起来,否则会导致编译错误。
- 如果 case 分支里的逻辑比较复杂,包含嵌套的 if-else 或 for 循环等,建议使用大括号使代码结构更清晰。
system("cls");//清屏操作
内存4区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值、局部变量、const修饰的局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时候由操作系统回收
代码区:
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外修改了它的指令
全局区(data,bss):
- 存放全局变量和静态变量以及常量
- 常量区中存放const修饰的全局常量和字符串常量(“hello world”)const修饰局部变量(局部常量)不在全局区
栈区(Stack):
- 由编译器自动分配释放,存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区(Heap):
- 堆区数据由程序员管理开辟和释放
- 堆区数据利用new关键字进行开辟内存(变量:int *p = new int(10); 数组:int *arr = new int[10];)
- 堆区数据利用delete关键字进行释放内存(delete p; delete[] arr;)
引用:
- 作用:给变量起别名
- 语法:数据类型 &别名 = 原名
- 作用:使用引用传递,形参修饰实参:
void swapNum(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
swapNum(a, b);
- 注意事项:引用必须初始化 && 引用在初始化后,不可改变
- 本质:引用的本质是一个指针常量
- 作用:const int &a;通常用来修饰形参值传递防止误操作
void printNum(const int &a, const int &b)
{
cout << “a: ” << a << endl;
cout << “b: ” << a << endl;
}
printNum(a, b);
函数高级:
- 函数的默认参数:
- 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
- 函数声明和函数定义时参数只能有一处有默认参数
- 占位参数(void func(int a = 10; int = 10);)
- 函数重载
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 / 个数不同 / 顺序不同
- 注意事项:引用作为重载条件(可以):
void func(int &a)
{
cout << "int &a" << endl;
}
void func(const int &a)
{
cout << "const int &a" << endl;
}
func(a);
func(10);
- 注意事项:函数重载碰到函数默认参数(可能会产生二义性):
void func(int a, int = 10)
{
cout << "int a, int" << endl;
}
void func(int a)
{
cout << "int &a" << endl;
}
func(a);(错误)
func(a, 10);(正确)
类和对象:
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为
- 封装
- 类中的属性和行为 我们统一称为成员
- 属性 成员属性 成员变量
- 行为 成员函数 成员方法
class Student(类)
{
public:
//属性
string name;
int number;
//行为
void printMessage()
{
cout << "姓名: " << name << "\t";
cout << "学号: " << number << endl;
}
void setName(string tName)
{
name = tName;
}
void setNumber(int tNumber)
{
number = tNumber;
}
};
- 访问权限:
公共权限public 成员 类内可以访问 类外可以访问
保护权限protected 成员 类内可以访问 类外不可以访问 继承中子类可以访问父类保护内容
私有权限private 成员 类内可以访问 类外不可以访问 继承中子类不可以访问父类私有内容
- class与struct的区别:默认权限不同(class默认权限私有pricate struct默认权限公有public)
- 成员属性设为私有
作用:1.保护原有的成员属性不可外部更改 2.使用公共的方法读和写私有成员属性达到访问成员属性的目的3.可以检查通过方法给成员赋的值是否合理
- 应用1:判断两个立方体是否相等,全局函数判断(两个参数),class成员函数判断长宽高(一个参数)
应用2:点和圆的关系,本类中可以用另一个类作为本类的成员
- 类内变量命名规则(m_xxx) member变量 与全局变量或局部变量做区分
- 封装类:
.c文件中:Point::setX(int X){return m_X};Point是作用域
.h文件:#prajma once
#include <iostream>
- 对象特性
- 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己的信息数据保证安全。
- C++中的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置。
- 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题:
一个对象或者变量没有初始状态,对其使用后果是未知。(构造函数)
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。(析构函数)
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对对象销毁系统自动调用,执行一些清理工作。
- 构造函数语法:
类名() {}
构造函数,没有返回值也不写 void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
- 析构函数语法:
~类名() {}
析构函数,没有返回值也不写 void
函数名称与类名相同,在名称前加上符号 ~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁时会自动调用析构,无须手动调用,而且只会调用一次(例子:栈区)
- 构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造(默认构造,注意:使用默认构造函数时不加括号 原因:Person a(); 编译器会认为是函数的声明而不是实例化对象)
- 按类型分为:普通构造和拷贝构造
例:
// 无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
// 有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
// 拷贝构造函数
Person(const Person &p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
三种调用方式:
括号法:
Person p1; // 默认构造函数调用
Person p2(10); // 有参构造函数
Person p3(p2); // 拷贝构造函数
显示法:
Person p1; // 默认构造
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 拷贝构造
Person(10); // 匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
Person(p3); //注意:不要利用拷贝构造函数初始化匿名对象 编译器会认为 Person(p3) === Person p3;
隐式转换法:
Person p4 = 10; // 相当于 写了 Person p4 = Person(10); 有参构造
Person p5 = p4; // 拷贝构造
- 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值:
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
以值方式返回局部对象:
Person doWork2()
{
Person p1;
return p1;
}
void test03()
{
Person p = doWork2();
}
- 4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不再提供其他构造函数
以下是图片中的文字提取结果:
---
- 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作(如果成员变量里面有指针开辟的空间,就需要用深拷贝)
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person(const Person &p)
{
cout << "Person 拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认实现就是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
栈释放:先进后出
- 初始列表
作用:
C++ 提供了初始化列表语法,用来初始化属性
语法:
构造函数():属性1(值1), 属性2(值2) ... {}
// 初始化列表初始化属性
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{
}
- 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
例如:
class A {};
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员。
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
- 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。
静态成员分为:(通过对象访问、通过类名访问 Person::func();)
- 静态成员变量:
- - 所有对象共享同一份数据
- - 在编译阶段分配内存
- - 类内声明,类外初始化
- 静态成员函数:
- - 所有对象共享一个函数
- - 静态成员函数只能访问静态成员变量
- C++对象模型和this指针
- 成员变量和成员函数分开存储
- 在C++中,类内的成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
- 空对象占用的字节数为1
- this指针概念
通过1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用 return *this(链式编程思想)
// 链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为:" << p2.age << endl;
- 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
void showPersonAge()
{
// 报错原因是因为传入的指针是为NULL
if (this == NULL)
{
return;
}
cout << "age = " << this->m_Age << endl;
}
void test01()
{
Person * p = NULL;
// p->showClassName();
p->showPersonAge();
}
- const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
// this指针的本质 是指针常量 指针的指向是不可以修改的
// const Person * const this;
// 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson() const
{
this->m_B = 100;
// this->m_A = 100;
// this = NULL; // this指针不可以修改指针的指向的
}
int m_A;
mutable int m_B; // 特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
void test02()
{
const Person p; // 在对象前加const,变为常对象
// p.m_A = 100;
p.m_B = 100; // m_B是特殊值,在常对象下也可以修改
// 常对象只能调用常函数
p.showPerson();
// p.func(); // 常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}
- 友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现
- 全局函数做友元
// 建筑物类
class Building
{
// goodGay全局函数是Building好朋友,可以访问Building中私有成员
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_SittingRoom; // 客厅
string m_BedRoom; // 卧室
};
- 类做友元
class Building
{
// GoodGay类是本类的好朋友,可以访问本类中私有成员
friend class GoodGay;
public:
Building();
};
- 成员函数做友元
class Building
{
// 告诉编译器 GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();
public:
Building();
};
- 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
- 加号运算符重载
作用:实现两个自定义数据类型相加的运算
自定义类 Person:
class Person
{
public:
int m_A;
int m_B;
};
使用 Person 类的示例:
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;(此时不能实现对象相加)
- 通过成员函数实现自定义加法逻辑:
Person PersonAddPerson(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
编译器给重载运算符定义了一个通用名称
- 通过成员函数重载 + 运算符:
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
Person p3 = p1.operator+(p2); // 简化后
Person p3 = p1 + p2;
- 通过全局函数重载 + 运算符
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person p3 = operator+(p1, p2); // 简化后
Person p3 = p1 + p2;
- 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于其最后使用的方式非常特殊的应用,因此称为函数
- 函数调用有直接写法,非常灵活
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int temp = myadd(5, 10);
cout << "temp = " << temp << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
}
- 继承
- 基本语法
- 减少重复代码
- 语法: class 子类: 继承方式 父类
- 子类 也称为 派生类
- 父类 也称为 基类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
};
class Java: public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
void test()
{
Java java;
java.header();
java.left();
java.footer();
java.content();
}
- 继承方式
- 继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是在类内部可见,但是还是会继承下去
};
//公共继承
class Son : public Base //占用16个字节
{
public:
int m_D;
};
- 构造和析构顺序
- 先构造父类,再构造子类,析构的顺序与构造的相反
- 继承同名成员处理方式
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
int m_A;
};
void test01()
{
Son s;
cout << "m_A = " << s.m_A << endl;
cout << "Base m_A = " << s.Base::m_A << endl;
}
- 同名静态函数处理
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
class Base
{
public:
static int m_A;
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
};
int Son::m_A = 200;
void test01()
{
Son s;
//通过对象的方式访问
cout << "Son m_A = " << s.m_A << endl;
cout << "Base m_A = " << s.Base::m_A << endl;
//通过类名的方式访问
cout << "Son m_A = " << Son::m_A << endl;
cout << "Base m_A = " << Son::Base::m_A << endl;
}
- 多继承语法
- 实际开发中不太建议使用多继承
- 加作用域
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
class Son :public Base1, public Base2
{
public:
Son()
{
m_B = 200;
}
int m_B;
};
void test01()
{
Son s;
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;
}
- 菱形继承
- 虚继承(关键字virtual)
class Animal
{
public:
int m_Age;
};
class Sheep :virtual public Animal {};
class Tuo :virtual public Animal {};//指向同一块地址
class SheepTuo :public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 28;
st.Tuo::m_Age = 28;
st.m_Age = 28;
//两句cout等价
cout << "st.m_Age = " << st.m_Age << endl;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
}
- 多态
- 多态的基本语法
- 动态多态的满足条件
- 有继承关系
- 子类重写父类的虚函数
- 动态多态使用
- 父类的指针或者引用 执行子类对象
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 do_speak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
do_speak(cat);
Dog dog;
do_speak(dog);
}
- 多态的原理刨析(虚函数表)
- 计算机类
- 多态的好处
- 组织结构清晰
- 可读性强
- 对于前期和后期扩展以及维护性高
//利用多态实现计算器
class AbstractCalculator
{
public:
virtual ~AbstractCalculator()
{
}
virtual int Get_Result()
{
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCalculator :public AbstractCalculator
{
public:
int Get_Result()
{
return m_Num1 + m_Num2;
}
};
class SubCalculator :public AbstractCalculator
{
public:
int Get_Result()
{
return m_Num1 - m_Num2;
}
};
class MulCalculator :public AbstractCalculator
{
public:
int Get_Result()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
AbstractCalculator *abs =new AddCalculator;
abs->m_Num1 = 100;
abs->m_Num2 = 100;
cout << abs->m_Num1 << "+" << abs->m_Num2 << "=" << abs->Get_Result() << endl;
delete abs;
abs = new SubCalculator;
abs->m_Num1 = 100;
abs->m_Num2 = 100;
cout << abs->m_Num1 << "-" << abs->m_Num2 << "=" << abs->Get_Result() << endl;
delete abs;
abs = new MulCalculator;
abs->m_Num1 = 100;
abs->m_Num2 = 100;
cout << abs->m_Num1 << "*" << abs->m_Num2 << "=" << abs->Get_Result() << endl;
}
- 纯虚函数和抽象类
- 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
- 当类中有了纯虚函数,这个类也称为抽象类
class AbstractDrinking
{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PutInCup() = 0;
virtual void PutSomething() = 0;
virtual ~AbstractDrinking()
{}
void MakeDrink()
{
Boil();
Brew();
PutInCup();
PutSomething();
}
};
class Coffee :public AbstractDrinking
{
public:
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
virtual void PutInCup()
{
cout << "倒入杯中" << endl;
}
virtual void PutSomething()
{
cout << "加入牛奶和糖" << endl;
}
};
class Tea :public AbstractDrinking
{
public:
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void PutInCup()
{
cout << "倒入杯中" << endl;
}
virtual void PutSomething()
{
cout << "加入柠檬" << endl;
}
};
void doWork(AbstractDrinking *abs)
{
abs->MakeDrink();
delete abs;
}
void test01()
{
AbstractDrinking *abs = new Coffee;
abs->MakeDrink();
delete abs;
}
- 虚析构和纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现(需要有声明 也需要类似空实现)
虚析构和纯虚析构的区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
class CPU
{
public:
virtual void calculate() = 0;
virtual ~CPU(){}
};
class GPU
{
public:
virtual void display() = 0;
virtual ~GPU(){}
};
class Memory
{
public:
virtual void storage() = 0;
virtual ~Memory(){}
};
class Computer
{
public:
Computer(CPU *cpu, GPU *gpu, Memory *mem)
{
m_cpu = cpu;
m_gpu = gpu;
m_mem = mem;
}
void Work()
{
m_cpu->calculate();
m_gpu->display();
m_mem->storage();
}
~Computer()
{
if(m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if(m_gpu != NULL)
{
delete m_gpu;
m_gpu = NULL;
}
if(m_mem != NULL)
{
delete m_cpu;
m_mem = NULL;
}
}
private:
CPU *m_cpu;
GPU *m_gpu;
Memory *m_mem;
};
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU正在工作" << endl;
}
};
class IntelGPU :public GPU
{
public:
virtual void display()
{
cout << "Intel的GPU正在工作" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的Memory正在工作" << endl;
}
};
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU正在工作" << endl;
}
};
class LenovoGPU :public GPU
{
public:
virtual void display()
{
cout << "Lenovo的GPU正在工作" << endl;
}
};
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的Memory正在工作" << endl;
}
};
void test01()
{
CPU *IntelCpu = new IntelCPU;
GPU *IntelGpu = new IntelGPU;
Memory *IntelMem = new IntelMemory;
Computer *computer1 = new Computer(IntelCpu, IntelGpu, IntelMem);
computer1 ->Work();
delete computer1;
cout << "----------------------------" << endl;
Computer *computer2 = new Computer(new LenovoCPU, new LenovoGPU, new LenovoMemory);
computer2->Work();
delete computer2;
cout << "----------------------------" << endl;
Computer *computer3 = new Computer(new IntelCPU, new IntelGPU, new LenovoMemory);
computer3->Work();
delete computer3;
}
- 文件操作
写文件步骤如下:
1. 包含头文件
#include <fstream>
2. 创建流对象
ofstream ofs;
3. 打开文件
ofs.open("文件路径", 打开方式);
4. 写数据
ofs << "写入的数据";
5. 关闭文件
ofs.close();
文件打开方式:
打开方式 解释
ios::in 为该文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 添加方式打开文件
ios::trunc 如果文件存在则删除,再创建
ios::binary 二进制打开模式
注意:文件打开方式可以组合使用,利用 | 操作符
例如:用二进制方式打开文件 ios::binary || ios::out
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
读取文件与文件步骤相似,但是读取方式相比于较多
该文件读取步骤如下:
1. 包含头文件
#include <stream>
2. 创建流对象
ifstream ifs;
3. 打开文件并判断文件是否打开成功
ifs.open("文件路径",打开方式);
4. 读取数据
四种方式读取
5. 关闭文件
ifs.close();
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if(!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
// 读数据
// 第一种
// char buf[1024] = {0};
// while(ifs >> buf)
// {
// cout << buf << endl;
// }
//第二种
// char buf[1024] = {0};
// if(ifs.getline(buf, sizeof(buf)))
// {
// cout << buf << endl;
// }
//第三种
// string buf;
//
// while(getline(ifs,buf))
// {
// cout << buf << endl;
// }
//
//第四种
// char c;
// while((c = ifs.get()) != EOF)
// {
// cout << c;
// }
ifs.close();
}
- 模板
- 模板的概念
- 模板就是通用的模具,大大提高复用性
- 函数模板
C++另一种编程思想称为泛型编程,主要利用的技术就是模板
C++提供两种模板机制:函数模板和类模板
- 函数模板语法:
函数模板作用:
建立一个通用函数,其函数返回类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template<typename T>
函数声明或定义
解释:
template ---声明创建模板
typename ---表明其后面的符合是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母。
Example:
//函数模板
template<typename T>
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
//1. 自动类型推导
//mySwap(a, b);
//2. 显示指定类型
mySwap<int>(a, b);
cout << "a =" << a << endl;
cout << "b =" << b << endl;
- 函数模板的注意事项
- 自动类型推导,必须推导出一致的数据类型T才可以使用。
- 模板必须要确定出T的数据类型,才可以使用。
- 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
Example:
//普通函数
int My_Add01(int a, int b)
{
return a + b;
}
template<class T>
T My_Add02(T a, T b)
{
return a + b;
}
void test01()
{
int a = 10;
//int b = 20;
char c = 'c';
cout << My_Add01(a, c) << endl;
//自动类型推导
cout << My_Add02(a, c) << endl;//error
//给出T的数据类型
cout << My_Add02<int>(a, c) << endl;
}
- 普通函数与函数模板的调用规则
- 如果函数模板和普通模板都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板 myPrint<>(a, b);
- 函数模板也可以发生重载
- 如果函数模板可以更好的匹配,优先调用函数模板
- 模板的局限性
- 类模板
Example:
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void Show_Person()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string, int>p1("孙悟空", 999);
p1.Show_Person();
}
- 类模板和函数模板的区别
类模板和函数模板的区别有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
example:
template<class NameType, class AgeType = int>
class Person {
public:
Person(NameType Name, AgeType Age)
{
this->m_Name = Name;
this->m_Age = Age;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string>p1("猪八戒", 999);
p1.showPerson();
}
- 类模板中成员函数的创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
- 类模板对象做函数参数
学习目标:
- 类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
- 指定传入类型 ---直接显示对象的数据类型
- 参数模板化 ---将对象中的参数变为模板进行传递
- 整个类模板化 ---将这个对象类型模板化进行传递
Example:
//类模板对象做函数参数
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void ShowPerson()
{
cout << "姓名: " << this->m_Name << " 年龄: " << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定传入类型
void printPerson1(Person<string, int>&p)
{
p.ShowPerson();
}
void test01()
{
Person<string, int>p("孙悟空", 100);
printPerson1(p);
}
//2、参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>&p)
{
p.ShowPerson();
cout << "T1的类型为: " << typeid(T1).name() << endl;
cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int>p("猪八戒", 90);
printPerson2(p);
}
//3、整个类模板化
template<class T>
void printPerson3(T &p)
{
p.ShowPerson();
cout << "T1的类型为: " << typeid(T).name() << endl;
}
void test03()
{
Person<string , int>p("唐僧",30);
printPerson3(p);
}
- 类模板与继承
- 当子类继承的父类是一个模板时,子类在声明的时候,要指定出父类中T的类型。
- 如果不指定,编译器无法给子类分配内存
- 如果想要灵活制定出父类中T的类型,子类也需要变为类模板。
Example:
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son :public Base //错误的 必须要知道父类中T的类型,才能继承给子类
class Son :public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想灵活指定父类中T的类型,子类也需要变成类模板
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:
Son2()
{
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
T1 obj;
};
void test02()
{
Son2<int, char>S2;
}
- 类模板成员函数类外实现
- 能够掌握类模板中的成员函数类外实现
template<class T1, class T2>
class Person {
int YourVar;
public:
Person(T1 name, T2 age);
// {
// this->m_Name = name;
// this->m_Age = age;
// }
void ShowPerson();
// {
// cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
// }
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>:: ShowPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
void test01()
{
Person<string, int>p("Tom", 20);
p.ShowPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
- 类模板分文件编写(.hpp)
hpp:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person {
int YourVar;
public:
Person(T1 name, T2 age);
// {
// this->m_Name = name;
// this->m_Age = age;
// }
void ShowPerson();
// {
// cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
// }
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>:: ShowPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
Cpp:
#include <iostream>
using namespace std;
#include <string>
#include "Person.hpp"
- 类模板与友元
学习目标:
- 掌握类模板配合友元函数的类内类外实现
- 全局函数类内实现 - 直接在类内声明友元即可
- 全局函数类外实现 - 需要提前让编译器知道全局函数的存在
template<class T1,class T2>
class Person {
friend void printPerson(Person<T1,T2>p)
{
cout << "姓名: " << p.m_Name << "年龄:" << p.m_Age << endl;
}
public:
Person(T1 name, T2 age)
{
this ->m_Name = name;
this ->m_Age = age;
}
T1 m_Name;
T2 m_Age;
};
- STL初识
- STL的诞生
- 长久以来,软件界一直希望建立一种可重复利用的东西
- C++的面向对象和泛型编程的思想,目的就是复用性的提升
- 大多数情况下,数据结构与算法都未能有一套标准,导致被迫重复做大量重复工作
- 为了建立数据结构与算法的一套标准,诞生了STL
- STL的基本概念
- STL(模板标准库)
- STL从广义上分为:容器、算法、迭代器
- 容器和算法之间通过迭代器进行无缝连接
- STL几乎所有的代码都采用了模板类或者模板函数
- STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构、如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法、如sort、find、copy、for_each等。
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
- STL中容器、算法、迭代器
容器:置物之所也
STL容器就是将广泛运用的某些数据结构实现出来。
常用的数据结构:数组、链表、栈、队列、集合、映射等等。
这些容器分为序列式容器和关联式容器两种:
序列式容器:强烈顺序的容器,序列容器中的每个元素均有固定的位置。
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。
算法:问题之解决也
有限的步骤,解决逻辑数学上的问题,这门学科我们叫做算法(Algorithms)。
算法分为:质量算法和非质量算法。
质量算法:是指运算过程中特定区域内的元素的内容。例如排序、查找、删除等等。
非质量算法:是指在运算过程中不要求区域内的元素内容,例如查找、计数、遍历、寻找极值等等。
送代器:容器和算法之间粘合剂
提供一种方法,使之能够将程序访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的送代器。
送代器使用非常类似于指针,初学阶段我们可以先理解送代器为指针。
- Vector存放内置数据类型
Example:
#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>
//vector容器存放内置数据类型
void myPrint(int val)
{
cout << val << endl;
}
void test01()
{
//创建一个vector容器、数组
vector<int> v;
//向容器中插入数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通过迭代器访问容器中的数据
// vector<int>::iterator itBegin = v.begin();
// vector<int>::iterator itEnd = v.end();
//
// while(itBegin != itEnd)
// {
// cout << *itBegin << endl;
// itBegin ++;
// }
//第二种for循环的遍历方式
// for(vector<int>::iterator it = v.begin();it != v.end();it ++)
// {
// cout << *it << endl;
// }
//第三种遍历方式 利用STL提供遍历算法
for_each(v.begin(), v.end(), myPrint);
}
int main()
{
test01();
system("pause");
return 0;
}