c++知识点1

#include 后面使用 尖括号 <>双引号 "" 的主要区别

写法 搜索顺序 用途
#include <file> 仅系统/编译器指定的 include 目录 标准库、第三方库
#include "file" 1. 当前目录 → 2. 系统目录(退化行为) 项目自定义头文件

函数的分文件编写

作用:让代码结构更加清晰

函数份文件编写一般有4个步骤

  1. 创建后缀名.h的头文件
  2. 创建后缀名cpp的源文件
  3. 在头文件中写函数的声明
  4. 在源文件中写函数的定义

空指针和野指针

空指针

空指针:指针变量指向内存中编号为0的空间
用途 :初始化指针变量
注意:空指针指向的内存是不可以访问的

指针常量和常量指针

类型 声明形式 能否改指向? 能否改值? 常见用途
常量指针 const int* p ✅ 可以 ❌ 不可以 保护数据不被意外修改
指针常量 int* const p ❌ 不可以 ✅ 可以 固定指向某个地址(如硬件寄存器)
两者皆常量 const int* const p 完全只读引用

指针函数 和函数指针

指针函数 和函数指针-CSDN博客

名称 是什么? 声明示例 关键特征
指针函数 返回指针的函数 int* foo(); 函数,返回值是指针
函数指针 指向函数的指针 int (*p)(int); 变量,存储函数地址

结构体指针

作用:通过指针访问结构体中的成员

  • 利用**操作符->**可以通过结构体指针访问结构体属性

内存分区模型

C++程序在执行时,将内存大方向分为4个区域

代码区 :存放函数体的二进制代码,由操作系统进行管理的
全局区: 存放全局变量和静态变量以及常量
栈区 :由编译器自动分配释放,存放函数的参数值,局部变量等
堆区: 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:

不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

C++ 和 Java 创建对象的区别

C++ 和 Java 创建对象的区别-CSDN博客

c++的数组和Java数组的不同

c++的数组和Java数组的不同-CSDN博客

值传递指针传递(地址传递)引用传递的区别

值传递指针传递(地址传递)引用传递的区别

cpp 复制代码
//1、值传递
void mySwop01(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}

//2、地址传递
void mySwop02(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3、引用传递
void mySwop03(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
特性 值传递 地址传递(指针) 引用传递
能否修改原变量 ❌ 不能 ✅ 能 ✅ 能
传参方式 func(x, y) func(&x, &y) func(x, y)
函数内操作对象 副本 通过 *p 解引用操作原变量 直接操作原变量(别名)
安全性 最安全(隔离) 可能空指针崩溃 安全(引用不能为空)
性能 小类型快,大对象慢(拷贝) 快(只传地址) 快(无拷贝,无解引用开销)
C 语言支持 ❌(C++ 特有)
  • 想修改原变量? → 优先用 引用传递int&)。
  • 不想修改,且对象很大? → 用 const T& 避免拷贝。
  • 需要表示"可选"参数(可能为空)? → 用指针(如 int* p)。
  • 只是读小数据(如 int)? → 值传递即可,简单清晰。

引用做函数返回值

作用:引用是可以作为函数的返回值存在的

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

用法:函数调用作为左值

cpp 复制代码
#include<iostream>
using namespace std;

//引用做函数的返回值
//1、不要返回局部变量的引用
int& test01()    //相当于现在test01()函数是  a的别名了,a可以做左值,函数当然也可以
{
	int a = 10;   //局部变量存放在四区中的 栈区
	return a;
}
//2、函数的调用可以作为左值
int& test02()
{
	static int a = 10;  //静态变量,存放在全局区,全局区上的数据在程序结束后释放
	return a;
}

int main()
{
	int& ref = test01();
	cout << "ref=" << ref << endl;   //正确,编译器做了保留
	cout << "ref=" << ref << endl;   //错误,乱码,因为a的内存已经释放

	int& ref2 = test02();    
	cout << "ref2=" << ref2 << endl;   
	cout << "ref2=" << ref2 << endl;   //ref2是a的别名

	test02() = 1000;   //如果函数的返回值是引用,这个函数调用可以作为左值
	cout << "ref2=" << ref2 << endl;
	cout << "ref2=" << ref2 << endl;

	system("pause"); 
	return 0;
}

函数占位参数

  • C++中函数的形参列表中可以有占位参数,用来做占位,调用函数时必须填补该位置。

要用于以下场景:

  • 保持函数签名兼容(如重载、模板特化)
  • 满足接口要求但不需要实际使用该参数
  • 为未来扩展预留参数位置
  • 与 C 风格回调函数兼容
cpp 复制代码
void func(int, double, char);  // 声明:三个占位参数

void func(int, double x, char) {  // 定义:只用了 x
    cout << "x = " << x << endl;
}

int main() {
    func(10, 3.14, 'A');  // 调用时必须传三个实参!
}

函数重载--引用 const

函数重载注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数
cpp 复制代码
#include<iostream>
using namespace std;

//函数重载的注意事项
//1、引用作为重载的条件
void func(int& a)
{
	cout << "func(int& a)调用" << endl;
}

void func(const int& a)   //类型不同
{
	cout << "func(const int& a)调用" << endl;
}

int main()
{
	int a = 10;
	func(a);    //调用没有const的函数
	func(10);    //调用有const的函数
	system("pause");
	return 0;
}

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

静态成员(类内声明,类外初始化)

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

  • 静态成员变量:所有对象共享同一份数据,在编译阶段分配内存,类内声明,类外初始化(类内初始化会报错)
  • 静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量

成员变量和成员函数分开存储

cpp 复制代码
#include<iostream>
using namespace std;

//成员变量 和 成员函数 分开存储的

class base {

};

void test01() {
	base p;
	//空对象占用内存空间为:1
	//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	//每个空对象也应该有一个独一无二的内存地址
	cout << "sizeof(p):" << sizeof(p) << endl;   //Person类里面无内容:1字节
}

class Person {
	int m_A;               //非静态成员变量  属于类的对象上
	static int m_B;        //静态成员变量  不属于类对象上

	void func() {}          //非静态成员函数   不属于类对象上
	static void func2() {}  //静态成员函数   不属于类对象上
};
int Person::m_B = 0;
void test02() {
	Person p;
	cout << "sizeof(p):" << sizeof(p) << endl;   //Person类里面有int m_A:4字节
}

int main() {
	test01();
	test02();
	system("pause");
	return 0;
}

this指针概念

在C++中成员变量和成员函数是分开存储的

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

cpp 复制代码
#include<iostream>
using namespace std;

//空指针调用成员函数

class Person {
public:
	void showClassName() {
		cout << "this is Person class" << endl;
	}

	void showPersonAge() {
		//报错, 原因是传入的指针是为NULL
		if (this == NULL) {
			return;
		}
		cout << "age=" << m_Age << endl;
	}

	int m_Age;
};

void test01() {
	Person* p = NULL;

	p->showClassName();
	p->showPersonAge();
}

int main() {
	test01();
	system("pause");
	return 0;
}

const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

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

运算符重载++

cpp 复制代码
class MyClass {
public:
    // 前置 ++:无参数
    MyClass& operator++();          // 返回引用

    // 后置 ++:带一个 int 参数(仅用于区分,不使用)
    MyClass operator++(int);        // 返回值(副本)
};

// 前置
Counter& operator++() {
    ++value;
    return *this;
}

// 后置:复用前置
Counter operator++(int) {
    Counter old = *this;  // 拷贝当前状态
    ++(*this);            // 调用前置++
    return old;           // 返回旧值
}

左移运算符重载<<

作用:可以输出自定义数据类型

cpp 复制代码
#include <iostream>
#include <string>

class Person {
private:
    std::string name;
    int age;

public:
    Person(const std::string& n, int a) : name(n), age(a) {}

    // 声明友元函数:允许访问 private 成员
    friend std::ostream& operator<<(std::ostream& os, const Person& p);
};

// 定义友元函数(非成员)
std::ostream& operator<<(std::ostream& os, const Person& p) {
    os << "Person(name: " << p.name << ", age: " << p.age << ")";
    return os;  // 返回 os 以支持链式调用(如 cout << a << b;)
}

int main() {
    Person p("Alice", 30);
    std::cout << p << std::endl;  // ✅ 输出:Person(name: Alice, age: 30)
    return 0;
}

继承中构造和析构顺序

在 C++ 的继承体系 中,构造函数和析构函数的调用顺序是有严格规定的

构造顺序:从基类到派生类(先父后子)

先构造基类 → 再构造派生类

📌 规则细节:

  1. 首先调用最顶层基类的构造函数。
  2. 然后逐层向下,依次调用中间基类(如果有多重继承,则按声明顺序)。
  3. 最后调用派生类自身的构造函数
  4. 如果有成员对象 ,它们的构造发生在该类构造函数体执行之前(按声明顺序)。

析构顺序:从派生类到基类(先子后父)

先析构派生类 → 再析构基类

📌 规则细节:

  1. 先调用派生类的析构函数
  2. 然后逐层向上,依次调用基类的析构函数(顺序与构造相反)。
  3. 成员对象的析构发生在该类析构函数体执行之后 ,按声明的逆序

带成员对象的完整示例

cpp 复制代码
#include <iostream>

class Member {
public:
    Member(const char* name) { std::cout << name << " 成员构造\n"; }
    ~Member() { std::cout << "成员析构\n"; }
};

class Base {
    Member m1{"Base.m1"};
public:
    Base() { std::cout << "Base 构造函数体\n"; }
    ~Base() { std::cout << "Base 析构函数体\n"; }
};

class Derived : public Base {
    Member m2{"Derived.m2"};
public:
    Derived() { std::cout << "Derived 构造函数体\n"; }
    ~Derived() { std::cout << "Derived 析构函数体\n"; }
};

int main() {
    Derived d;
}
cpp 复制代码
Base.m1 成员构造
Base 构造函数体
Derived.m2 成员构造
Derived 构造函数体
Derived 析构函数体
成员析构          // m2
Base 析构函数体
成员析构          // m1

继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

菱形继承-----虚继承

菱形继承(Diamond Inheritance) 是 C++ 多重继承中的一种特殊结构,指一个派生类通过两条不同的继承路径继承了同一个基类,形成类似"菱形"的继承图。它会引发 数据冗余二义性 问题。

解决方案:虚继承(Virtual Inheritance)

使用 virtual 关键字BC 共享同一个 A 的实例

cpp 复制代码
class A {
public:
    int x = 10;
    void show() { std::cout << "A::show, x=" << x << "\n"; }
};

class B : virtual public A {};   // 虚继承
class C : virtual public A {};   // 虚继承

class D : public B, public C {}; // D 只有一个 A 的副本

int main() {
    D d;
    d.x = 42;           // ✅ 无二义性!只有一个 x
    d.show();           // ✅ 正常调用
    std::cout << d.x << "\n"; // 输出: 42
}

虚继承确保:无论通过多少条路径继承,共同基类 AD 中只有唯一一份实例。

c++的多态

序号 名称 实际归属 说明
1 重载多态(Overloading Polymorphism) 编译时 函数/运算符重载
2 强制多态(Coercion Polymorphism) 编译时 隐式类型转换(如 intdouble
3 参数多态(Parametric Polymorphism) 编译时 模板(泛型)
4 包含多态(Inclusion Polymorphism) 运行时 虚函数 + 继承(子类型多态)
  1. 编译时多态(静态多态)
  • 编译阶段就确定调用哪个函数。
  • 实现方式:
    • 函数重载(Function Overloading)
    • 运算符重载(Operator Overloading)
    • 模板(Templates) → 泛型编程的核心

📌 特点:无运行时开销,效率高。

  1. 运行时多态(动态多态)
  • 程序运行时根据对象实际类型决定调用哪个函数。
  • 实现方式:
    • 虚函数(virtual functions) + 继承
    • 通过基类指针/引用调用派生类重写的函数

📌 特点:灵活,但有虚表(vtable)和间接调用的开销。

c++中只有虚函数才能实现动态多态,否则成员函数调用由指针引用的静态类型决定

cpp 复制代码
#include <iostream>

class Base {
public:
    void show() { std::cout << "Base::show\n"; }  // ❌ 非虚函数
};

class Derived : public Base {
public:
    void show() { std::cout << "Derived::show\n"; }
};

int main() {
    Derived d;
    Base* p = &d;          // 静态类型是 Base*,动态类型是 Derived*

    p->show();             // 输出: Base::show
}

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0;

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

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

通常情况下,纯虚函数没有函数体(即不提供实现),
但 C++ 允许为纯虚函数提供函数体!

也就是说:

🔹 可以没有函数体 (最常见)

🔹 也可以有函数体(合法且有用)

纯虚函数可以有函数体吗?✅ 可以!

虽然声明为 = 0,但你仍然可以在类外(或 C++11 起在类内)提供定义

cpp 复制代码
#include <iostream>

class Base {
public:
    virtual void func() = 0;  // 声明为纯虚
};

// 提供函数体(类外定义)
void Base::func() {
    std::cout << "Base::func() called\n";
}

class Derived : public Base {
public:
    void func() override {
        Base::func();  // 显式调用基类的纯虚函数实现
        std::cout << "Derived::func()\n";
    }
};

int main() {
    Derived d;
    d.func();
}
cpp 复制代码
Base::func() called
Derived::func()

虚析构和纯虚析构

在 C++ 多态中,虚析构函数纯虚析构函数 是解决通过基类指针安全释放派生类对象 的关键机制。它们的核心目标是:确保派生类的析构函数被正确调用,避免内存泄漏

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象
cpp 复制代码
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}

问题背景:为什么需要虚析构?

cpp 复制代码
class Base {
public:
    ~Base() { cout << "Base 析构\n"; }
};

class Derived : public Base {
private:
    int* data;
public:
    Derived() { data = new int(42); }
    ~Derived() { 
        delete data; 
        cout << "Derived 析构\n"; 
    }
};

int main() {
    Base* p = new Derived();
    delete p;  // ❌ 只调用 Base::~Base()!
}

后果

  • Derived::~Derived() 未被调用
  • data 指向的堆内存未释放 → 内存泄漏

原因:非虚析构函数是静态绑定 ,编译器只看指针类型(Base*),不关心实际对象类型。

解决方案 1:虚析构函数

cpp 复制代码
class Base {
public:
    virtual ~Base() {  // 加 virtual 关键字
        cout << "Base 虚析构\n";
    }
};
int main() {
    Base* p = new Derived();
    delete p; 
    // 输出:
    // Derived 析构
    // Base 虚析构
}

结果

  • 通过基类指针 delete 时:
    1. 先调用 派生类析构函数
    2. 再调用 基类析构函数
  • 确保资源完整释放

为什么纯虚析构必须有函数体?

  • 析构函数的特殊性
    即使声明为纯虚,派生类析构时仍会自动调用基类析构函数
  • 如果没有定义,链接器找不到 Base::~Base() 的实现 → 链接错误

C++11 及以后:允许类内定义

cpp 复制代码
class Base {
public:
    virtual ~Base() = 0 {
        // 函数体直接写在这里!
        std::cout << "Pure virtual destructor\n";
    }
};
相关推荐
凯子坚持 c1 小时前
Qt常用控件指南(9)
开发语言·qt
ONE_PUNCH_Ge1 小时前
Go 语言泛型
开发语言·后端·golang
舟舟亢亢1 小时前
JVM复习笔记——下
java·jvm·笔记
rainbow68891 小时前
Python学生管理系统:JSON持久化实战
java·前端·python
冉佳驹2 小时前
C++11 ——— 列表初始化、移动语义、可变参数模板、lamdba表达式、function包装器和bind包装器
c++·可变参数模板·移动构造·移动赋值·function包装器·bind包装器·lamdba表达式
有味道的男人2 小时前
1688获得商品类目调取商品榜单
java·前端·spring
leaves falling2 小时前
c语言单链表
c语言·开发语言
xu_yule2 小时前
算法基础—组合数学
c++·算法
独自破碎E2 小时前
【中心扩展法】LCR_020_回文子串
java·开发语言