c++学习
分文件编写
1.创建.h后缀名的头文件
2.创建.cpp后缀名的源文件
3.在.h里声明函数
4.在.cpp里编写函数
指针
int main() {
int a;
cin >> a;
int *p = &a;
cout << a << ' ' << *p << ' ' << p << endl;
*p = 2023;
cout << a << ' ' << *p << ' ' << p;
return 0;
}
通过*p直接改变a的数值,p为a的地址,不发生改变
指针的数据类型占用4个字节(32位) 8个(64位)
空指针
int *p = NULL;
空指针 >> 不可访问
野指针
int *p = (int *)0x1100;
野指针 >> 指向非法空间
利用指针访问数组
int arr[3] = { 0, 1, 2 };
int* p = arr;
for (int i = 0; i < 3; i++) {
cout << *p << endl;
cout << p << endl;
p++;
}
指针和函数
值传递无法改变实参
地址传递可以改变实参
例
#include<iostream>
using namespace std;
void swap(int* p1, int* p2) {
int t = *p1;
*p1 = *p2;
*p2 = t;
}
int main() {
int a = 10;
int b = 20;
swap(&a, &b);
cout << a << ' ' << b;
return 0;
}
const
常量指针 >>> 指针的指向可以修改,但指针的值不可以改
const int *p = &a;
*p = 20; 错误
p = &b; 正确
指针常量 >>> 指针的指向不可以改,指针指向的值可以改
int * const p = &a;
*p = 20; 正确
p = &b; 错误
同时被修饰 >>> 指针的指向和指向的值都不可以改
const int * const p = &a;
*p = 20; 错误
p = &b; 错误
结构体
struct Student {
string name;
int age;
float score;
}s3;
int main() {
//创建变量时,struct可以省略
//创建方式1
struct Student s1;
s1.name = "yzb";
s1.age = 19;
s1.score = 100;
//创建方式2
struct Student s2 = { "YZB",19,100 };
//创建方式3
//创建结构体时同时创建结构体变量
s3.name = "YYY";
s3.age = 20;
s3.score = 80;
return 0;
}
结构体数组
struct Student {
string name;
int age;
float score;
};
int main() {
Student stuArray[3] =
{
{"张三",18,100},
{"李四",19,99},
{"马武",20,92}
};
for (int i = 0; i < 3; i++) {
cout << stuArray[i].name << ...
}
return 0;
}
结构体指针
int main() {
Student s1 = { "yzb",19,100 };
Student* p = &s1;
cout << p->name;
return 0;
}
结构体嵌套
struct student {
string name1;
string name2;
string name3;
};
struct teachar {
string name;
int id;
int age;
student name0;
};
int main() {
teachar t1 = { "MALI",001,29,"王虎","马六","小李" };
cout << t1.name0.name1;
return 0;
}
结构体const使用
无法改变结构体的内容,防止误操作
void PrintStudent(const Student* s) {
s->age = 18;
cout << s->age;
};
int main() {
Student s = { "李四",001,99 };
PrintStudent(&s);
return 0;
}
代码四区
代码区:存放函数体的二进制代码,由操作系统进行管理的。
代码区存放CPU执行的机器指令既程序汇编而成的二进制代码。
代码区是共享的,对于频繁执行的程序,内存中只需要保存一份即可。
代码区是只读的,只读的原因是防止程序意外修改了指令。
全局区:存放全局变量和静态变量以及常量
局部变量和局部常量所在地址和其他变量相距很远,不在全局区之中。
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等。
不要返回局部变量的地址,栈区开辟的地址由编译器自动释放。
堆区:由程序员分配和释放,若程序员不释放,程序结束后由操作系统回收。
主要利用new关键字开辟堆区
int* func()
{
int* a = new int(10);
return a;
}
int main(void) {
int* p;
p = func();
cout << *p << endl;
return 0;
}
利用new关键字,将数据开辟在堆区,指针本质也是局部变量,放在栈上,但指针保存的数据放在堆区
new的使用和释放
int* func() {
int* p = new int(100);//在堆区创建整型数据,new返回是该数据类型的指针
return p;
}
void test(){
int* p = func();
cout << *p << endl;//堆区数据由程序员直接释放
delete p;//释放数据
cout << *p << endl;//无法输出
}
int main() {
test();
return 0;
}
int *arr = new int [10] //开辟大小为10的数组
返回的为首地址 通过首地址继续修改范围
delete [] arr; //释放数组
引用
int a = 10;
创建引用
int& b = a;
注意: 1.创建必须初始化
2.引用一旦初始化无法改变;
引用传递
使形参修饰实参
void swapab(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
swapab(a, b);
cout << a << b;
}
引用做函数的返回值
//引用做函数的返回值 >> 不要返回局部变量的引用
int& test1() {
int a = 10;
return a;
}
int& test2() {
static int a = 10;//静态变量,在全局区
return a;
}
//函数的调用可以作为左值,继续赋值操作
int main() {
int& b = test2();
cout << b << endl;
}
引用的本质为指针常量 int* const ref = &a; >>> int &ref = a
常量引用
主要用于修饰形参,防止误操作
void test(const int &a)//加const ,a的数值无法改变,防止误操作
int main() {
int a = 10;
int& ref = 10;//引用必须引用合法内存空间
const int& ref = 10;//加上const代码修改为了 int temp = 10; const int & ref = temp;
ref = 20; //无法修该,变成只读
}
函数高级
函数默认参数
int add(int a, int b = 20, int c = 30){
return a + b + c;
}
如果某个位置有了默认参数,这个位置往后都得有默认值;
声明中有默认参数则函数实现中不能有,只能有一部分有;
函数占位参数
void fun(int a, int){
....
}
函数重载
函数名相同,提高复用性
条件 1.同一个作用域
2.函数名称相同
3.函数参数类型不同或者个数不同或者
4.仅返回类型不同的不可以作为重载条件
引用作为重载条件
void test1(int& a) {
...
}
void test1(const int& a) {
...
}
int main() {
int a = 10;
test1(a);调用上面的
test1(10);调用下面的
}
函数重载碰到默认参数
void test1(int a) {
...
}
void test1(int a, int = 20) {
...
}
int main() {
int a = 10;
test1(a);//编译器无法判断,出现歧义
}
类和对象
面向对象的三大特性 封装 继承 多态
封装
意义:1.将属性和行为作为一个整体,表现生活中的事物
语法
class 类名{访问权限:属性/行为};
const double P = 3.14;
class Circle
{
public:
int mr;
double calculateZC() {
return 2 * P * mr;
}
void mrr(int a){
mr = a;
}
};
int main() {
Circle c1;//实例化
c1.mr = 10; // 或者 c1.mrr(10);
cout << c1.calculateZC();
}
类中属性和行为统一称为成员
属性 >> 成员属性 成员变量
行为 >> 成员函数 成员方法
意义:2.将属性和行为放在不同权限下,加以控制
权限
class默认权限为私有
public公共权限 成员类内可以访问,类外可以访问;
protected保护权限 成员类内可以访问,类外不可以访问;子类可以访问父类;
private私有权限 成员类内可以访问,类外不可以访问;子类不可以访问父类;
class和struct区别va
默认访问权限不同 struct默认权限为公有,class默认权限为私有
成员属性设置为私有
1.将所有成员属性设置为私有,可以自己控制读写权限
2.对于写权限,我们可以检测数据的有效性
对象特性
对象的初始化和清理
构造函数语法 类名(){}
可以有参数,可以发生重载 会自动调用,只调用一次
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
Person(const Person &p)//拷贝构造函数
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
cout << "年龄:" << age << endl;
}
调用方式
1.括号法
Person p1;调用默认构造函数使,不加()
Person p2(10);
Person p3(p2);
2.显示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
Person(10);匿名对象 当前行执行完系统会立即回收匿名对象
不要利用拷贝构造函数,初始化匿名对象
3.隐式转化法
Person p4 = 10;
Person p5 = p4 ;
拷贝构造函数的调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.以值方式返回局部对象
构造函数的调用规则
默认情况 一个类会添加3个函数
1.默认构造函数 2.默认析构函数 3.默认拷贝构造函数
规则 用户定义有参构造函数,则c++不提供无参构造,但会提供默认拷贝函数
如果用户定义拷贝构造函数,c++不提供其他构造函数
深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
初始化列表
语法:构造函数():属性1(值1),属性2(值2)....{}
class Person
{
public:
//初始化列表
Person(int a, int b, int c) :m_a(a), m_b(b), m_c(c) {};
public:
int m_a, m_b, m_c;
};
void main()
{
Person p(2, 3, 4);
cout << "a=" << p.m_a << endl;
cout << "b=" << p.m_b << endl;
cout << "c=" << p.m_c << endl;
}
静态成员
成员变量前加 static
1、 所有对象共享同一份数据
2、 在编译阶段分配内存
3、 类内声明,类外初始化
class person{
static int m_A;
};
int person::m_A = 0;
静态成员函数
1、 所有对象共享同一个函数
2、静态成员函数只能访问静态成员变量
3、静态成员函数也有访问权限,写在private 里,类外是访问不到的
析构函数语法 ~类名(){}
不可以有参数,不可以发生重载 会自动调用,只调用一次
构造和析构都必须有,我们不提供,编译器会有一个空实现
c++对象模型和this指针
类内成员变量和成员函数分开储存,只有非静态成员变量才属于类的对象上
空对象占用内存空间为 1 ,每个空对象都有独一无二的内存地址
只有非静态成员变量属于类的对象上;
this指针
每一个非静态成员函数只会诞生一份函数实例,多个同类型的对象会共用一块代码
1.this指针不需要定义,直接使用即可
2.当形参和成员变量同名时,可用this指针来区分
Person(int age)
{
this->age = age;
}
3.在类的非静态成员函数中返回对象本身,可使用return *this
4.注意空指针访问的问题
const修饰成员函数
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
- 常对象可以修改静态变量的值
友元
全局函数做友元
friend void goodgay(Building * building);
类做友元
friend class goodgay;
成员函数做友元
friend void goodgay::vist1();//注意要写上作用域,不能只写函数;
运算符重载
operator 加需要重载的符号
+重载
#include<iostream>
using namespace std;
class P {
public:
int a;
int b;
/*P operator+(P& p) {
P temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}成员函数重载加号*/
};
P operator+(P &p1,P &p2) {
P temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}//全局函数重载加号
int main() {
P p1;
p1.a = 1;
p1.b = 3;
P p2;
p2.a = 1;
p2.b = 3;
P p3 = p1 + p2;
cout << p3.a << ' ' << p3.b;
}
调用本质
Person p3 = p1.operator+(p2);
Person p3 = operator+(p1,p2);
<<重载
#include<iostream>
using namespace std;
class P {
public:
int a;
int b;
};
ostream& operator << (ostream& cout, P& p1) {
cout << p1.a << ' ' << p1.b;
return cont;
}
void test() {
P p1;
p1.a = 1;
p1.b = 3;
cout << p1 << endl;
}
int main() {
test();
}
++重载
#include<iostream>
using namespace std;
class P {
public:
int a;
//前置递增
P& operator ++() {
this->a++;
return *this;
}
//后置递增
P operator++(int) {
P temp = *this;
a++;
return temp;
}
};
ostream& operator<<(ostream& os, const P& p1) {
os << p1.a;
return os;
}
int main() {
P p1;
p1.a = 6;
cout << p1++ << endl; //输出p1原始值6
cout << p1 << endl; //输出递增后的p1值7
return 0;
}
= 重载
#include<iostream>
using namespace std;
class P {
public:
int a;
P(int a) :a(a) {};
P& operator=(const P& p) {
/*if (a != NULL) {
delete a;
a = NULL;
}数据是否在堆区,在则释放*/
a = p.a;
return *this;
}
};
int main() {
P a(10);
P b(20);
P c(30);
a = b = c;
cout << a.a << ' '<< b.a << ' ' << c.a;
}
关系运算符重载
== 重载(举例)
#include<iostream>
using namespace std;
class P {
public:
int a;
P(int a) :a(a) {};
};
bool operator==(const P& p1, const P& p2) {
if (p1.a == p2.a) {
return true;
}
else return false;
}
int main() {
P a(10);
P b(20);
if (a == b) {
cout << "相等";
}
else cout << "不等";
}
() 重载 >>> 仿函数
无固定写法,很灵活,自己玩去
继承
减少重复代码
class 子类 :继承方式 父类
子类也叫做派生类 父类也叫做基类
继承方式 :公共继承 保护继承 私有继承
父类中所有非静态成员属性都会被子类继承下去,私有成员属性被编译器隐藏了,因此访问不到,但依旧被继承了
查看对象模型
开发人员命令提示工具 跳转盘符 C: cd 路径 cl /d1 reportleSingClassLayout 类名 文件名
继承中先调用父类构造函数,在调用子类构造函数析构函数,析构顺序与构造相反
同名处理方式 >> 加作用域
同名成员属性
Son s;
cout << s.a << s.Base::a << endl;
同名成员函数
Son.s;
s.func();
s.Base::func();
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
想访问到被隐藏的函数,加作用域即可
对于同名静态成员
1.对象访问
Son s;
cout << s.a << s.Base::a << endl;
2.类名访问
Son s;
cout << Son::a << Son::Base::a << endl;
多继承语法
class 子类 : 继承方式 父类1 ,继承方式 父类2
菱形继承
出现相同数据时 加作用域可以进行访问 但两份数据 会导致资源浪费
需要使用虚继承的方式解决这个问题 继承前加 virtual
变为虚继承 vbptr
虚基类指针
#include<iostream>
using namespace std;
class A{
public:
int age;
};
class B:virtual public A {};
class C:virtual public A {};
class D :public B, public C {};
int main()
{
D test;
test.age = 18;
cout << test.age << endl;
test.D::age = 19;
cout << test.age << endl;
test.C::age = 20;
cout << test.age << endl;
}
多态
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include<iostream>
using namespace std;
class Animal{
public:
virtual void speak(){ //函数前面加上virtual关键字,变成虚函数,编译阶段无法绑定他的地址
cout << "动物在说话" << endl;
}
};
class Cat : public Animal{
public:
void speak(){
cout << "喵喵" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "汪汪" << endl;
}
};
//地址早绑定 在编译阶段确定函数地址
//这里可以这样理解:比如你是开餐馆的,早绑定就是一个老顾客(假设他来这只吃面条)来吃饭,你一看是这个老顾客就直接上面条(编译阶段直接按父类的走)。
//而晚绑定是新顾客,虽然来了但无法直接判断他吃,还得新顾客说自己吃什么才能上什么(看实际传入的子类是什么)
void dospeak(Animal &animal){
animal.speak();
}
void test01(){
Cat cat;
dospeak(cat);
Dog dog;
dospeak(dog);
}
int main(){
test01();
system("pause");
}
多态满足条件:
1、有继承关系
2、子类重写父类中的虚函数
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态使用:
父类指针或引用指向子类对象
纯虚函数和抽象类
纯虚函数语法 virtual 返回值类型 函数名 (参数列表) = 0;
当类中有纯虚函数则这个类称为抽象类
抽象类特点: 无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
案例
//抽象制作饮品
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 {
public:
//烧水
virtual void Boil() {
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入牛奶!" << endl;
}
};
//制作茶水
class Tea : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入枸杞!" << endl;
}
};
void DoWork(AbstractDrinking* drink) {
drink->MakeDrink();
delete drink;
}
void test01() {
DoWork(new Coffee);
DoWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
总结:
-
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
-
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
-
拥有纯虚析构函数的类也属于抽象类
多态案例
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
class CPU {
public:
virtual void calculat() = 0;
};
class VoiCard{
public:
virtual void display() = 0;
};
class Memory{
public:
virtual void storage() = 0;
};
class Computer{
public:
Computer(CPU * cpu,VoiCard * vc,Memory * mem){
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
void Work (){
m_cpu ->calculat();
m_vc ->display();
m_mem ->storage();
}
~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;
VoiCard * m_vc;
Memory * m_mem;
};
class Icpu : public CPU{
virtual void calculat() {
cout << "英特尔cpu开始工作" << endl;
}
};
class Ivc : public VoiCard{
virtual void display() {
cout << "英特尔显卡开始显示" << endl;
}
};
class Imem : public Memory{
virtual void storage() {
cout << "英特尔内存开始储存" << endl;
}
};
class Lcpu : public CPU{
virtual void calculat() {
cout << "联想cpu开始工作" << endl;
}
};
class Lvc : public VoiCard{
virtual void display() {
cout << "联想显卡开始显示" << endl;
}
};
class Lmem : public Memory{
virtual void storage() {
cout << "联想内存开始储存" << endl;
}
};
int main(){
CPU * C = new Icpu;
VoiCard * V = new Ivc;
Memory * M = new Imem;
cout << "第一台电脑开始工作" << endl;
Computer * CP = new Computer(C,V,M);
CP -> Work();
delete CP;
cout << "-------------------" << endl;
cout << "第二台电脑开始工作" << endl;
Computer * CP2 = new Computer(new Lcpu,new Lvc,new Lmem);
CP2 -> Work();
delete CP2;
}
文本操作
操作时加文件流头文件
#include<fstream>
文件类型分 文本文件和二进制文件
操作文件三大类 ofstream:写文件 ifstream:读文件 fstream:读写文件
写文件操作流程
1.包含头文件
#include<fstream>
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open("文件类型",ios::out);
4.写数据
ofs << "写入的数据";
5.关闭文件
ofs.close();
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意: 文件打开方式可以配合使用,利用 | 操作符
例如: 用二进制方式写文件
ios::binary | ios::out
读文件流程
1.包含头文件
#include<fstream>
2.创建流对象
ifstream ifs;
3.打开文件并判断是否打开
ifs.open("文件类型",ios::in);
if(!ifs.is_open()){
cout << "打开失败" << endl;
return;
}
4.读数据
四种方式举例
#include<iostream>
#include<cstring>
#include<cmath>
#include<fstream>
using namespace std;
void test01(){
ifstream ifs;
ifs.open("test.txt",ios::out);
if(!ifs.is_open()){
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//char a[1024] = { 0 };
//while (ifs >> a)
//{
// cout << a << endl;
//}
//第二种
//char a[1024] = { 0 };
//while (ifs.getline(a,sizeof(a)))
//{
// cout << a << endl;
//}
//第三种
string a;
while (getline(ifs, a))
{
cout << a << endl;
}
//第四种
/*char a;
while ((a = ifs.get()) != EOF)
{
cout << a;
}*/
ifs.close();
}
int main(){
test01();
}
5.关闭文件
ifs.close();
二进制文件操作
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :
ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二进制文件 写文件
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:
istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
杂
system(“pause”);
“pause”这个系统命令的功能很简单,就是在命令行上输出一行类似于“Press any key to exit”的字,等待用户按一个键,然后返回。