Featured image of post C++ 核心编程

C++ 核心编程

记录 C++ 的学习

内存分区模型

程序运行前

编译后生成 exe 可执行程序,分为两个区域

代码区:

  • 存放CPU执行的机器指令
  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
  • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

  • 全局变量和静态变量存放在此
    • 局部变量(即使用 const 修饰)不在全局区中
  • 全局区还包含了常量区,字符串常量和其他常量也存放在此
    • const 修饰的全局常量、字符串常量
  • 该区域的数据在程序结束后由操作系统释放

程序运行后

栈区:

  • 由编译器自动分配释放,存放函数的参数值,局部变量等
    • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放(如函数的形参、局部变量)

堆区:

  • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  • 在 C++ 中主要利用 new 在堆区开辟内存
1
2
3
4
5
6
7
int * func ()
{
    //利用new关键字可以将数据开辟到堆区
    //指针本质也是局部变量,放在栈上,指针保存的数据是放在堆区
    int * p=newint(10) ;
    return p;
}

new 操作符

C++中利用new操作符在堆区开辟数据

  • 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

    • delete 对应指针
  • 利用new创建的数据,会返回该数据对应的类型的指针

1
2
3
4
// 创建数组
int* a = new int(length);
//释放数组的时候要加[]才可以,指明一段内存空间
delete[] arr;

引用

给变量起别名

  • 变量的含义:用于指代,操作某块内存
1
2
// 语法:数据类型&别名=原名
int &b = a;

注意事项

  1. 引用必须初始化
  2. 初始化后不可再改变

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//1.值传递
void mySwap01int a,int b{
    int temp=a;
    a = b
    b = temp;
}
//2.地址传递
void mySwapθ2int *a,int *b{
    int temp*a;
    *b;
    *b=temp;
}
//3.引用传递,别名和本名可以相同
void mySwap03int &aint &b{
    int temp =a;
    a = b
    b = temp;
}

引用做返回值

注意:不要返回局部变量引用

用法:函数调用作为左值

1
2
3
4
5
6
7
//2、函数的调用可以作为左值
int& test02(
{
    staticinta=10;//静态变量,存放在全局区,全局区上的数据在程序结束后系统释放
	return a;
}
test02() = 1000 // 支持

引用的本质

本质是指针常量,由编译器转换为指针

1
2
3
//自动转换为 int*constref=&a;指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref=a;
ref = 20//内部发现 ref 是引用,自动帮我们转换为:*ref=20;

常量引用

1
2
3
4
5
//常量引用
//使用场景:用来修饰形参,防止误操作
//int& a = 10; 这行错误,非法空间
//加上const之后编译器将代码修改int temp = 10;const int &rettemp;
const int& ref=10//引用必须引一块合法的内存空间

函数

默认参数

C++中,函数的形参列表中的形参是可以有默认值的

1
2
3
int func(int a,int b = 20  int c = 30){
	return a + b + c;
}
  • 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
  • 如果函数声明有默认参数,函数实现就不能有默认参数

占位参数

1
2
3
4
//函数占位参数,占位参数也可以有默认参数
void funcint aint{
	cout<<thisisfunc"<<endl;
}

重载

作用:函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同或者个数不同或者顺序不同
    • 返回值不同不可以作为条件

注意

  • 引用作为重载条件

    • 以 const 为标志的区分,是否可写
  • 数重载碰到函数默认参数

    • 出现二义性,报错

类和对象

封装

意义:

  • 属性和行为作为一个整体,表现生活中的事物
  • 属性和行为加以权限控制

语法:class 类名 { 访问权限:属性/行为 };

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class circle {
    public:
    	int r;
    	double calculateC(){
            return 2 * PI * r;
        }
}

Circle c1; // 实例化
c1.r = 10; // 给属性赋值

类中的属性和行为统一称为成员

  • 成员属性 –> 成员变量
  • 成员函数 –> 成员方法

访问权限

public:类内可以访问,类外可以访问

protected:类内可以访问,类外不可以访问,继承类可访问

private:类内可以访问,类外不可以访问。

Struct 和 Class

唯一的区别就在于默认的访问权限不同

  • struct 默认权限为公共
  • class 默认权限为私有

成员属性私有化

优点:

  • 所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性

用 set 、get 方法控制权限、操作

对象的初始化和清理

构造函数和析构函数

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

    • 没有返回值也不写void
    • 函数名称与类名相同 类名(){}
    • 构造函数可以有参数,因此可以发生重载
    • 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

    • 没有返回值也不写void
    • 函数名称与类名相同,在名称前加上符号 ~
    • 析构函数不可以有参数,因此不可以发生重载
    • 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

构造函数分类与调用

分类:

  • 按参数分为:有参构造和无参构造
    • 使用默认无参构造时,不要加括号,否则会认为是函数声明
  • 按类型分为:普通构造和拷贝构造
    • 不要用拷贝构造函数初始化匿名对象,编译器会认为 Person(p3) === Person p3
1
2
3
4
//拷贝构造函数
Person(const Person& p{
	age =p.age;
}

调用方式:括号法,显式法,隐式转换法

1
2
3
4
//1、括号法
Person p1//默认构造函数调用
Person p2(10)//有参构造函数
Person p(p2)//拷贝构造函数
1
2
3
4
5
//2、显示法
Person p1; //不能加括号
Person p2=Person(10)//有参构造
Person p3=Personp2);//拷贝构造
Person(10)//匿名对象特点:当前行执行结束后,系统会立即回收掉匿名对象
1
2
3
//3、隐式转换法
Person p4 = 10;//相当于写了Person p4 =Person(10);有参构造
Person p5 = p4;// 拷贝构造

拷贝构造函数的调用时机

  1. 使用一个已经创建完毕的对象来初始化一个新对象
  2. 传递的方式给函数参数传值
  3. 值方式返回局部对象
    • 在函数内部创建的对象被返回时,会创建一个新的对象返回

拷贝构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数:

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 认拷贝构造函数,对属性进行值拷贝

定义有参构造函数,C++ 不再提供默认无参构造,但是会提供默认拷贝构造

定义拷贝构造函数,C++ 不会再提供其他构造函数

深浅拷贝

浅:简单的赋值拷贝

  • 默认提供的是浅拷贝

深:在堆内存重新创建一块内存,进行拷贝

  • 涉及到申请堆区时,需要深拷贝

初始化列表

1
2
3
4
5
//初始化列表初始化属性
Person(int a, int b,int c) :m_A(a),m_B(b), m_C(c)
{
    ...
}

类作为类成员

当类有嵌套时,会先构造内部对象,析构顺序与构造相反

静态成员

静态成员变量:

  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

静态成员函数:

  • 所有对象共享同一个函数
  • 只能访问静态成员变量
    • 因为调用函数时,无法定位非静态成员变量(属于特定对象)

可以通过对象,也可以直接通过类名访问,有访问权限

内存模型和 this 指针

内存:

  • 类内的成员变量和成员函数分开存储

  • 只有非静态成员变量才属于类的对象上

this 指针:

  • 指向被调用的成员函数所属的对象,隐含在每一个非静态成员函数内
  • 当形参和成员变量同名时,可用this指针来区分
    • this -> name
  • 在类的非静态成员函数中返回对象本身,可使用 return *this
    • 实现链式编程
1
2
3
4
5
6
Person& PersonAddAge(Person &p)
{
    this->age += p.age;
    //this指向p2的指针,而*this指向的就是p2这个对象本体
    return *this;
}

注意返回 Person**&**,值返回会调用拷贝函数创建一个新的对象

空指针访问成员函数

1
2
3
Person * p = NULL;
p->showClassName ();
// 只要所调用的函数中没有使用到 this 即可(包含隐含的 this,用于访问属性)

const 修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字mutable后,则在常函数中也可以修改

1
2
3
4
5
6
//this指针的本质是指针常量指针的指向是不可以修改的
const Person * const this; // 设置指针指向的值也无法修改
void showPersonO() const{
    this->m_A = 100;
}
//this=NULL;  //this指针不可以修改指针的指向

常对象:

  • 声明对象前加const称该对象为常对象
  • 对象只能调用常函数

友元

使一个函数或者类访问另一个类中私有成员,关键字friend

三种实现:

  • 局函数做友元
  • 类做友元
  • 成员函数做友元
    • friend void clazz::method();

在类中声明 friend,被声明的可以访问本类的私有属性

运算符重载

定义自定义类型的运算方式

成员函数重载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//1、成员函数重载+号,本质调用:Person p3 =pl.operator+(p2);
Person operator+(Person &p)
{
    Person temp;
    temp.m_A=this->m_A +p.m_A
    temp.m_B =Ithis->m_B +p.m_B;
    return temp;
}
//2、全局函数重载+号,本质调用:Person p3 = operator+(pl,p2);
Person operator+(Person &pl,Person &p2)
{
    Person temp;
    temp.m_A =pl.m_A +p2.m_A;
    temp.m_B=pl.m_B+p2.m_B;
    return temp;
}

左移

只能通过全局函数重载

1
2
3
4
5
6
//只能利用全局函数重载左移运算符
void operator<<(ostream &cout,Person &p)//本质: operate<< (cout,p)
{
    cout << p.name;
    return cout;
}

递增

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//重载前置++运算符返回引用为了一直对一个数据进行递增操作
MyInteger& operator++()
{
    //先进行++运算
    m_Num++;
    //再将自身做返回
    return *this;
}

//重载后置++运算符
//void operator++(int) int代表占位参数,可以用于区分前置和后置递增
MyInteger operator++(int)
{
    //先记录当时结果
    MyInteger temp = *this;
    //后递增
    m_Num++;
    7/最后将记录结果做返回
    return temp;
}

赋值

默认实现为浅拷贝,p1 = p2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 重载赋值运算符
Person& operator=(Person &p)
{
    if (m_Age != NULL)
    {
        delete m_Age;
        m_Age = NULL;
    }

    // 编译器提供的代码是浅拷贝
    // m_Age = p.m_Age;

    // 提供深拷贝 解决浅拷贝的问题
    m_Age = new int(*p.m_Age);

    // 返回自身
    return *this;
}

注意:返回引用,可以操作自身;返回值,会拷贝出一个对象。目的是为了实现连等

关系

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
bool operator==(Person &p)
{
    if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
    {
        return true;
    }
    else
    {
        return false;
    }
}

函数调用 ()

仿函数

  • 非常灵活
1
2
3
4
5
//重载函数调用运算符
void operator() (string test)
{
    cout<< test << endl;
}

继承

下级别的成员除了拥有上一级的共性,还有自己的特性,抽取出父、子

  • 用于减少重复代码
1
2
3
4
class son : public father
{
    ...
}

继承方式

image-20251013182119744

向下压级,私有被隐藏,但还是会继承

继承的对象模型

父类中所有非静态成员属性都会被子类继承下去

构造、析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

  • 先有父,后有子;析构相反

同名成员处理

  • 父类加作用域
    • s.base::func();
    • 只要子类有同名,父类函数全都被隐藏(重载也不行)
  • 子类直接访问

同名静态成员

静态成员和非静态成员出现同名,处理方式一致

多继承

class son: type fa1,type fa2

不建议,父类命名可能重复

菱形继承

一出二,二合一

  • 孙类继承了两份父类的相同数据,产生冗余

  • 利用虚继承解决菱形继承

1
2
3
4
5
6
7
8
9
//利用虚继承解决菱形继承的问题
//继承之前加上关键字virtual变为虚继承
//Anima1类称为虚基类
[//羊类
class Sheep0:virtual public Animal{}
//驼类
class Tuo :virtual public Animal{};
//羊驼类
class SheepTuo :public ISheep, public Tuo{};

多态

基本概念

多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态
    • 有派生类时,动态绑定子类重写的虚函数(父类指针指向子类对象)

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定-编译阶段确定函数地址
  • 动态多态的函数地址晚绑定-运行阶段确定函数地址
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Animal
{
public:
    //Speak函数就是虚函数
    //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat :public Animal
{
public:
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

原理:

  • 子类重写父类虚函数时:子类中的虚函数表内部会替换成子类的虚函数地址

优点:

  • 满足“开闭原则”

扩展对外开放,修改对外关闭

  • 组织结构清晰,可读性强
  • 可维护性强

纯虚函数和抽象类

virtual 返回值类型 函数名(参数列表)= θ;

当类中有了纯虚函数,这个类也称为抽象类

  • 抽象类无法实例化
  • 子类必须重写父类的纯虚函数,否则子类也是抽象类

虚析构与纯虚析构

共性:

  • 解决父类指针释放子类对象
    • 原因:父类指针指向子类对象,delete 的时候只调用父类的析构函数
  • 都需要有具体的函数实现

差异:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象
    • 虚析构函数需要被实现

若堆中没有数据,可以不写

文件操作

文本文件

文件以 ASCII 码存储

操作文件的三大类:

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作

写文件

步骤:

  • 包含头文件 #include <fstream>
  • 创建流对象 ofstream ofs;
  • 打开文件 ofs.open("文件路径",打开方式);
  • 写数据 ofs<<"写入的数据";
  • 关闭文件ofs.close()

二进制

ios::binary

Licensed under Calendar
最后更新于 Oct 14, 2025 00:00 UTC