C++速查手册

C++基本语法

简介: C++ 是一种通用的编程语言,支持过程式编程、面向对象编程和泛型编程。C++的基本语法类似于C语言。

详细介绍: C++ 的基本语法包括:

  • 关键字(如 if, else, for, while等)

    用于表示特定功能或操作的保留字

  • 标识符(用于命名变量、函数等)

    用于表示变量、函数等的名称

  • 数据类型(如 int, float, double等)

    用于表示变量的类型和大小

  • 声明和定义(如变量声明、函数声明等)

    用于声明变量或函数的类型、名称和作用

  • 控制结构(如 if, for, while等)

    用于控制程序的执行流程

  • 函数

    用于封装代码块以便于重复使用和模块化

  • 注释(用于解释代码的功能或作用)

    用于解释代码的功能或作用,不影响程序执行

应用场景: C++ 基本语法适用于各种类型的项目,包括桌面应用、服务器应用、嵌入式系统、游戏等。

C++ 数据类型

简介: C++ 数据类型用于表示变量的类型和大小。C++ 提供了一组内置的数据类型,如整数、浮点数、字符等。

详细介绍: C++ 的基本数据类型包括:

  • 整数类型(如 int, short, long等)
  • 浮点类型(如 float, double等)
  • 字符类型(如 char)
  • 布尔类型(如 bool)

语法用途模板:

  • 整数类型: 用于表示整数值
  • 浮点类型: 用于表示实数值(小数)
  • 字符类型: 用于表示单个字符
  • 布尔类型: 用于表示真(true)或假(false)

应用场景: C++ 数据类型适用于各种类型的项目,可用于表示各种类型的值,如计数器、金额、距离等。

语法详细代码:

#include <iostream>

int main() 
{
    int a = 10;            // 整数类型
    float b = 3.14f;       // 浮点类型
    char c = 'A';          // 字符类型
    bool d = true;         // 布尔类型

    std::cout << "a: " << a << std::endl;
    std::cout << "b: " << b << std::endl;
    std::cout << "c: " << c << std::endl;
    std::cout << "d: " << std::boolalpha << d << std::endl;

    return 0;
}

C++ 修饰符类型

简介: C++ 修饰符类型用于修改基本数据类型的属性,如有符号、无符号、长、短等。

详细介绍: C++ 支持以下修饰符类型:

  • signed: 表示有符号类型(可以是正数或负数)
  • unsigned: 表示无符号类型(只能是非负数)
  • long: 表示长整数类型(范围大于普通整数)
  • short: 表示短整数类型(范围小于普通整数)

语法用途模板:

  • signed: 用于定义有符号整数类型
  • unsigned: 用于定义无符号整数类型
  • long: 用于定义长整数类型
  • short: 用于定义短整数类型

应用场景: C++ 修饰符类型适用于各种类型的项目,用于根据需求调整数据类型的属性,如表示较大的整数、仅表示非负数等。

语法详细代码:

#include <iostream>

int main()
{
    // 定义有符号整数
    signed int a = -10;

    // 定义无符号整数
    unsigned int b = 20;

    // 定义长整数
    long int c = 1234567890L;

    // 定义短整数
    short int d = 1000;

    std::cout << "Signed integer: " << a << std::endl;
    std::cout << "Unsigned integer: " << b << std::endl;
    std::cout << "Long integer: " << c << std::endl;
    std::cout << "Short integer: " << d << std::endl;

    return 0;
}

C++ 存储类

简介: C++ 存储类用于描述变量的存储和作用域。它影响变量的生命周期和可见性。

详细介绍: C++ 支持以下存储类:

  • auto: 用于声明自动变量,存储在栈上。在 C++11 及以后的版本中,auto 关键字还可以用于自动类型推导。
  • register: 用于声明寄存器变量,存储在 CPU 寄存器中。这些变量的访问速度非常快,但数量有限。
  • static: 用于声明静态变量,存储在全局数据区。静态变量的生命周期是整个程序执行期间。
  • extern: 用于声明外部变量,即在其他文件中定义的变量。这允许在多个文件之间共享变量。

语法用途模板:

  • auto: 用于定义自动变量或自动类型推导
  • register: 用于定义寄存器变量
  • static: 用于定义静态变量
  • extern: 用于声明外部变量

应用场景: C++ 存储类适用于各种类型的项目,用于控制变量的存储和作用域,以实现代码的封装和模块化。

语法详细代码:

#include <iostream>

// 外部变量声明
extern int externVar;

// 静态变量定义
static int staticVar = 50;

void exampleFunction() 
{
    // 自动变量定义
    auto int localVar = 10;
    std::cout << "Local
	variable: " << localVar << std::endl;
	// 寄存器变量定义
    register int regVar = 20;
    std::cout << "Register variable: " << regVar << std::endl;
}

// 外部变量定义
int externVar = 30;

int main() 
{
    // 调用函数,显示自动变量和寄存器变量
    exampleFunction();
        // 显示静态变量
    std::cout << "Static variable: " << staticVar << std::endl;

    // 显示外部变量
    std::cout << "Extern variable: " << externVar << std::endl;

    return 0;
}

C++ 变量类型

简介: C++ 变量类型用于存储程序中的数据。变量类型是由数据类型定义的,并根据数据类型分配内存。

详细介绍: C++ 变量类型包括:

  • 局部变量:在函数内定义的变量,只能在函数内部使用。
  • 全局变量:在所有函数之外定义的变量,可以在整个程序中使用。
  • 静态变量:使用 static 关键字定义的变量,它们的值在程序执行期间保持不变。

语法用途模板:

  • 局部变量: 在函数内部使用的变量
  • 全局变量: 在整个程序中使用的变量
  • 静态变量: 在程序执行期间保持不变的变量

应用场景: C++ 变量类型适用于各种类型的项目,用于存储和管理数据。

语法详细代码:

#include <iostream>

// 全局变量
int globalVar = 100;

void exampleFunction() 
{
    // 局部变量
    int localVar = 10;
    std::cout << "Local variable: " << localVar << std::endl;
}

int main() 
{
    // 访问全局变量
    std::cout << "Global variable: " << globalVar << std::endl;
    // 调用函数,显示局部变量
    exampleFunction();
    return 0;
}

C++ 命名空间

简介: C++ 命名空间是一种用于组织代码的方式,避免名称冲突。

详细介绍: C++ 命名空间具有以下特点:

  • 使用 namespace 关键字定义命名空间
  • 支持嵌套命名空间和命名空间别名
  • 使用 using 声明或 using 指令引入命名空间成员

应用场景: C++ 命名空间适用于需要组织代码和避免名称冲突的项目,提高代码的可读性和可维护性。

语法详细代码:

#include <iostream>

// 定义命名空间
namespace ns1
{
    int x = 10;

    void display() 
    {
        std::cout << "ns1::display" << std::endl;
    }
}

namespace ns2
{
    int x = 20;

    void display() 
    {
        std::cout << "ns2::display" << std::endl;
    }
}

using ns2::x; // 展开ns2命名空间内的ns2内的x变量;
using namespace ns1;    // 展开命名空间ns1,内置的所有元素会直接放在全局区,就不再需要使用ns1作为域作用限定符访问元素了,但这样会破坏封装性


int main() 
{
    // :: 是域作用操作符,访问对应作用域内的数据
    // 需要用命名空间里的数据只需要  命名空间::变量名称 即可
    // 使用命名空间成员
    std::cout << "ns1::x = " << ns1::x << std::endl;
    std::cout << "ns2::x = " << ns2::x << std::endl;
    

    ns1::display();
    ns2::display();

    return 0;
}

命名空间细节

  • 命名空间元素使用操作符叫做——“域限定操作符”,并不是在C++里独有,C也有这个操作符。

    #include <stdio.h>
    #include <stdlib.h>
    
    int x = 0;
    
    int main()
    {
        int x = 10;
        // 域限定操作符: 到达指定的域寻找指定的元素。
        printf("%d\n", ::x);		// 前面留空白,是指去全局域寻找变量 x
        return 0;
    }
    
  • 命名空间里支持声明:变量、结构体、函数、模板以及命名空间;

    // 命名空间可以嵌套
    #include <stdio.h>
    #include <stdlib.h>
    
    namespace Space
    {
    	int rand = 0;
        int sub(int x, int y)
        {
            return x - y;
        }
        
        namespace Space2
        {
            int rand = 0;
            int sub(int x, int y)
            {
                return x - y;
            }
        }
    }
    
    int main()
    {
        printf("%d \n", Space::rand);
        printf("%d \n", Space::sub(2, 1));
        
        printf("%d \n", Space::Space2::rand);
        printf("%d \n", Space::Space2::sub(2, 1));
        return 0;
    }
    
    
  • 命名空间声明的元素属于全局变量,会放在静态区内;

  • 命名空间初始化时间与全局变量一致——在main函数运行之前;

  • 命名空间只能初始化声明在全局域中,同一个文件里命名空间之间也会出现命名冲突问题,在多个不同文件里,重名的命名空间会被合并

    // list.h
    namespace List
    {
        struct Node
        {
            // ...;
        }
        void ListInit();
        void ListPushBack();
    }
    
    // list.c
    namespace List
    {
        void ListInit()
        {
            // ...
        }
        void ListPushBack()
        {
            // ...
        }
    }
    

C++ 变量作用域

简介: C++ 变量作用域是变量在程序中的可见性和生命周期。变量作用域决定了在哪些代码段可以访问变量。

详细介绍: C++ 变量作用域包括:

  • 局部作用域:局部变量的作用域,只能在定义它们的代码块(如函数)内访问。
  • 全局作用域:全局变量的作用域,可以在整个程序中访问。
  • 类作用域:在类内部定义的变量,只能在类的成员函数中访问。
  • 命名空间作用域:在命名空间内定义的变量,可以在整个命名空间内访问。

语法用途模板:

  • 局部作用域: 限制变量只能在定义它们的代码块内访问
  • 全局作用域: 允许在整个程序中访问变量
  • 类作用域: 限制变量只能在类的成员函数中访问
  • 命名空间作用域: 允许在整个命名空间内访问变量

应用场景: C++ 变量作用域适用于各种类型的项目,用于控制变量的可见性和生命周期,以实现代码的封装和模块化。

语法详细代码:

#include <iostream>

// 全局作用域
int globalVar = 100;

// 命名空间作用域
namespace MyNamespace
{
    int namespaceVar = 200;
}

class MyClass
{
public:
    // 类作用域
    int classVar;
    void display() 
    {
        std::cout << "Class variable: " << classVar << std::endl;
    }
};

void exampleFunction() 
{
    // 局部作用域
    int localVar = 10;
    std::cout << "Local variable: " << localVar << std::endl;
}

int main() 
{
    // 访问全局作用域变量
    std::cout << "Global variable: " << globalVar << std::endl;
    // 访问命名空间作用域变量
    std::cout << "Namespace variable: " << MyNamespace::namespaceVar << std::endl;

    // 访问类作用域变量
    MyClass obj;
    obj.classVar = 300;
    obj.display();

    // 调用函数,显示局部作用域变量
    exampleFunction();

    return 0;
}

C++ 常量

简介: C++ 常量是固定值的标识符,其值在程序执行期间不能被改变。

详细介绍:
C++ 常量可以通过以下方式定义:

  • 使用 #define 预处理器指令定义常量
  • 使用 const 关键字定义常量,在C++中,被const修饰的变量会被赋予常属性,同#define宏定义作用一样。(在C语言中被const修饰的常变量本质还是属于变量)
  • 使用枚举常量(enum)

语法用途模板:

  • const 关键字: 用于定义常量的关键字,表示值不能被修改
  • #define 指令: 用于在预处理阶段定义常量,表示值不能被修改
  • 枚举常量: 用于定义一组整数常量,表示值不能被修改

应用场景: C++ 常量适用于各种类型的项目,用于表示不会在程序执行过程中改变的值,如圆周率、重力加速度等。

语法详细代码:

#include <iostream>

// 使用 const 关键字定义常量
const double PI = 3.14159;

// 使用 #define 预处理器指令定义常量
#define GRAVITY 9.81

// 使用枚举常量定义
enum WeekDays { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

int main()
{
    std::cout << "Constant PI: " << PI << std::endl;
    std::cout << "Constant GRAVITY: " << GRAVITY << std::endl;
    std::cout << "Constant MONDAY: " << MONDAY << std::endl;

    return 0;
}

C++ 运算符

简介: C++ 运算符用于对一个或多个操作数执行特定的操作,如算术运算、逻辑运算、比较运算等。

详细介绍:
C++ 支持以下运算符:

  • 算术运算符: +, -, *, /, %, ++, –
  • 关系运算符: ==, !=, >, <, >=, <=
  • 逻辑运算符: &&, ||, !
  • 位运算符: &, |, ^, ~, <<, >>
  • 赋值运算符: =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • 条件运算符(三目运算符): ?:
  • sizeof 运算符: 用于获取数据类型或对象的大小
  • 逗号运算符: 用于连接两个表达式,使它们按顺序求值
  • 类型转换运算符: 用于显式类型转换

应用场景: C++ 运算符适用于各种类型的项目,用于执行各种计算、逻辑判断和操作。

语法详细代码:

#include <iostream>

int main() 
{
    int a = 10, b = 20;

    // 算术运算
    std::cout << "a + b: " << a + b << std::endl;
    std::cout << "a - b: " << a - b << std::endl;
    std::cout << "a * b: " << a * b << std::endl;
    std::cout << "a / b: " << a / b << std::endl;
    std::cout << "a % b: " << a % b << std::endl;

    // 关系运算
    std::cout << "a == b: " << (a == b) << std::endl;
    std::cout << "a != b: " << (a != b) << std::endl;
    std::cout << "a > b: " << (a > b) << std::endl;
    std::cout << "a < b: " << (a < b) << std::endl;
    std::cout << "a >= b: " << (a >= b) << std::endl;
    std::cout << "a <= b: " << (a <= b) << std::endl;

    // 逻辑运算
    std::cout << "a && b: " << (a && b) << std::endl;
    std::cout << "a || b: " << (a || b) << std::endl;
    std::cout << "!a: " << (!a) << std::endl;
    // 位运算
    std::cout << "a & b: " << (a & b) << std::endl;
    std::cout << "a | b: " << (a | b) << std::endl;
    std::cout << "a ^ b: " << (a ^ b) << std::endl;
    std::cout << "~a: " << (~a) << std::endl;
    std::cout << "a << 2: " << (a << 2) << std::endl;
    std::cout << "a >> 2: " << (a >> 2) << std::endl;

    // 赋值运算
    a += b;
    std::cout << "a += b: " << a << std::endl;

    // 条件运算
    std::cout << "a > b ? a : b: " << (a > b ? a : b) << std::endl;

    // sizeof 运算
    std::cout << "sizeof(int): " << sizeof(int) << std::endl;

    // 逗号运算
    int c = (a = 5, b = 10, a + b);
    std::cout << "c: " << c << std::endl;

    // 类型转换运算
    float d = static_cast<float>(a) / static_cast<float>(b);
    std::cout << "d: " << d << std::endl;

    return 0;
}

C++ 控制结构

简介: C++ 控制结构用于在程序中实现条件判断、循环和跳转等功能。

详细介绍:
C++ 支持以下控制结构:

  • 顺序结构: 按照代码顺序执行语句
  • 选择结构: 使用 if, if-else, if-else if, switch 等语句实现条件判断
  • 循环结构: 使用 for, while, do-while 等语句实现循环
  • 跳转结构: 使用 break, continue, return, goto 等语句实现跳转

应用场景: C++ 控制结构适用于各种类型的项目,用于实现程序的逻辑控制和流程控制。

语法详细代码:

#include <iostream>

int main()
{
    int a = 5;

    // 顺序结构
    std::cout << "Sequential structure" << std::endl;

    // 选择结构
    if (a > 0)
    {
        std::cout << "a is positive" << std::endl;
    } 
    else 
    {
        std::cout << "a is non-positive" << std::endl;
    }

    // 循环结构
    for (int i = 0; i < a; i++) 
    {
        std::cout << "Loop iteration: " << i << std::endl;
    }

    // 跳转结构
    if (a > 0)
    {
        std::cout << "Jump to return" << std::endl;
        return 0;
    }

    std::cout << "This will not be executed" << std::endl;
    return 0;
}

C++ 指针

简介: C++ 指针是一种变量类型,用于存储内存地址。通过指针,我们可以间接访问和操作内存中的数据。指针在 C++ 中被广泛应用于动态内存分配、函数参数传递、数据结构和算法等场景。

详细介绍: C++ 指针具有以下特点:

  • 存储变量的内存地址
  • 可以进行指针运算,如递增和递减等
  • 可以被声明为 void 类型,表示通用指针

语法模板:

type* pointer_name;

应用场景: C++ 指针适用于需要操作内存地址的场景,如动态内存分配、数组操作、函数指针等。

语法详细代码:

#include <iostream>

int main() 
{
    int x = 42;
    int *ptr = &x;

    std::cout << "Value of x: " << x << std::endl;
    std::cout << "Address of x: " << &x << std::endl;
    std::cout << "Value of ptr: " << ptr << std::endl;
    std::cout << "Value pointed by ptr: " << *ptr << std::endl;

    return 0;
}

C++内存管理方式:动态内存

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理。

new/delete操作内置类型

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;
    
	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
    
	// 动态申请10个int类型的空间
	int* ptr6 = new int[3];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

注意:

  • new和delete是一个操作符,支持重载,不是宏。
  • 申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
  • 申请和释放单个元素的空间,使用new和delete操作符;
  • 申请和释放连续的空间,使用new[]和delete[]。

new/delete的理解

new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数,事实上new在底层是调用operator new全局函数来申请空间,同理delete在底层通过operator delete全局函数来释放空间。

// operator new 和operator delete 底层代码

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上述两个全局函数的实现知道:

  • operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常;

    抛异常并非是强制终止程序或返回空指针,而是使用try-catch语句进行异常捕获并处理,如果不捕获异常则会出现断言错误:未被捕获的异常。

    以下是捕获new申请空间失败异常的实例(以32位操作系统为例)

    #include <iostream>
    
    int main()
    {
    	try
    	{
    		char* p = new char[0x7fffffff];
    	}
    	catch (const exception & e)
    	{
    		cout << e.what() << endl;
    	}
    
    	return 0;
    }
    
    // 输出:bad allocation
    
  • operator delete 最终是通过free来释放空间的。

C++提出new和delete,主要是解决两个问题:

  1. 自定义类型对象自动申请的时候,初始化和清理的问题。new/delete会调用构造函数和析构函数
  2. new失败了以后要求抛异常,这样才符合面向对象语言的出错处理机制。

ps: delete和free一般不会失败,如果失败了,一般都是释放空间上存在越界或者释放指针位置不对。

new和delete的运行原理

  • 内置类型

    如果申请的是内置类型的空间,new和malloc,delete和free基本类似。

    不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

  • 自定义类型

    • new的原理

      1. 调用operator new函数申请空间;
      2. 在申请的空间上执行构造函数,完成对象的构造。
    • new T[N]的原理

      1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

      2. 在申请的空间上执行N次构造函数

    • delete的原理

      1. 在空间上执行析构函数,完成对象中资源的清理工作
      2. 调用operator delete函数释放对象的空间
    • delete[]的原理

      1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
      2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

定位new表达式(了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式:

  • new (place_address) type或者new (place_address) type(initializer-list);
  • 其中:place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

class Test
{
public:
	Test()
		: _data(0)
	{
		cout << "Test():" << this << endl;
	}
	~Test()
	{
		cout << "~Test():" << this << endl;
	}
private:
	int _data;
};

int main()
{
	
	Test* pt = (Test*)malloc(sizeof(Test));  
    // pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
    
	new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
	
	return 0;
}

malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

不同的地方是:

用法上的区别

  1. malloc和free是函数,new和delete是操作符;
  2. malloc申请的空间不会初始化,new可以初始化;
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可;
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;

底层上的区别

  1. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常;

  2. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

内存泄漏

概念

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

危害

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 如果这里Func函数抛异常,则导致 delete[] p3未执行=p3没被释放.
	delete[] p3;
}

void TalentFunc()
{
    // 因为这一行代码,你不仅会成为项目组里的交际花,还是公司里不可多得的人才
    void* p = new char[0xfffffffful];		
    return;
}
内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏**(Heap leak)**

    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  • 系统资源泄漏

    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何避免内存泄漏
  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结:

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

扩展:使用new开出4G的内存空间

int main()
{
 void* p = new char[0xfffffffful];
 system("pause")						// 打开任务管理器查看你的内存变化,切记,正常使用一定要注意释放掉!!
 return 0;

C++ 引用

简介: C++ 引用是一种别名机制,它允许为已存在的变量创建另一个名称。引用在内存中不占用额外空间,使用引用可以实现更高效和安全的函数参数传递、返回值等。

详细介绍: C++ 引用具有以下特点:

  • 必须在声明时进行初始化,不能为 NULL
  • 一旦初始化,不能重新绑定到另一个对象
  • 对引用的修改会直接影响到原始变量

语法模板:

type &reference_name = variable_name;

应用场景: C++ 引用适用于需要提高效率的场景,如作为函数参数传递,避免不必要的数据拷贝。

语法详细代码:

#include <iostream>

// 使用引用作为函数参数交换两个整数的值
void swap(int &x, int &y) 
{
    int temp = x;
    x = y;
    y = temp;
}

int main() 
{
    int a = 5;
    int b = 10;

    std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
    
    swap(a, b);
    
    std::cout << "After swap: a = " << a << ", b = " << b << std::endl;

    return 0;
}

在上述示例中,swap 函数接收两个整数引用作为参数。由于引用直接指向原始变量,因此在函数内部交换引用的值会直接影响到原始变量,从而实现了有效地交换两个整数的值。

C++ 引用与指针的区别

  1. 引用在定义时必须初始化,指针没有要求

  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

  3. 没有NULL引用,但有NULL指针

  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小6.有多级指针,但是没有多级引用;

  6. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  7. 引用比指针使用起来相对更安全。

C++ 函数

简介: C++ 函数是一段完成特定任务的独立代码块,可以接收输入参数并返回结果。

详细介绍:
C++ 函数具有以下特点:

  • 可以重复使用,提高代码的复用性
  • 可以实现代码的模块化,便于维护和管理
  • 可以接收输入参数,根据参数执行不同的操作
  • 可以返回一个值,表示执行结果

语法用途模板:

  • 函数声明: 描述函数的原型,包括返回类型、函数名和参数列表
  • 函数定义: 实现函数的具体功能,包括返回类型、函数名、参数列表和函数体
  • 函数调用: 在程序中使用函数,传递输入参数并获取返回结果

应用场景: C++ 函数适用于各种类型的项目,用于实现代码的模块化和复用。

语法详细代码:

#include <iostream>

// 函数声明
int add(int a, int b);

int main() 
{
    int a = 5, b = 10;

    // 函数调用
    int result = add(a, b);
    std::cout << "a + b = " << result << std::endl;

    return 0;
}

// 函数定义
int add(int a, int b)
{
    return a + b;
}

C++函数缺省参数

大家知道什么是备胎吗?出门远行路途遥远常常需要准备一个备胎,C++中函数的参数也可以配备胎。

缺省参数的概念

缺省参数是声明函数时为函数的参数指定一个默认值。在调用该函数时如果没有指定实参则采用该默认值,否则使用指定的实参。 缺省参数只能用在.h/.hpp文件内使用,否则编译失败。

void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func(); 	  	// 没有传参时,使用参数的默认值
	TestFunc(10); 	// 传参时,使用指定的实参
}
缺省参数的分类
  • 全缺省参数

    // 指定了全部参数
    void TestFunc(int a = 10, int b = 20, int c = 30)
    {
    	cout << "a = " << a << endl;
    	cout << "b = " << b << endl;
    	cout << "c = " << c << endl;
    }
    
  • 半缺省参数

    // 缺省部分参数 -- 必须从右往左缺省,必须连续缺省 未赋值的在前面,赋值的在后面
    void TestFunc(int a, int b = 10, int c = 20)
    {
    	cout << "a = " << a << endl;
    	cout << "b = " << b << endl;
    	cout << "c = " << c << endl;
    }
    

缺省参数的重点

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给;

  2. 缺省参数不能在函数声明和定义中同时出现,因为如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值;

    // 错误的写法
    // a.h
    void TestFunc(int a = 10);
    
    // a.c
    void TestFunc(int a = 20)
    {}
    // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。此时编译会提出重定义错误
    
    // 正确的写法:要么在声明,要么在定义。推荐写在声明
    // a.h
    void TestFunc(int a = 10);
    
    // a.c
    void TestFunc(int a)
    {}
    
  3. 缺省值必须是常量或者全局变量;

  4. C语言不支持(编译器不支持)。

C++函数重载

简介: 函数重载是 C++ 中的一种特性,允许在同一作用域内定义多个具有相同名称但参数列表不同的函数。编译器会根据函数调用时提供的参数自动选择合适的函数版本执行。

函数重载的形式和规则:

  1. 参数数量不同:重载的函数具有不同数量的参数。
  2. 参数类型不同:重载的函数具有相同数量但类型不同的参数。
  3. 参数顺序不同:重载的函数具有相同数量和类型的参数,但顺序不同。

注意:函数的返回类型不能作为重载的依据,即仅返回类型不同的函数无法实现重载。

示例代码:

#include <iostream>

// 原函数
void print(const std::string& str)
{
    std::cout << "void print(const std::string& str)" << std::endl;
}

// 函数重载 - 参数数量不同
void print(const std::string& str, int times) 
{

    std::cout << "void print(const std::string& str, int times) " << std::endl;

}

// 函数重载 - 参数类型不同
void print(int num)
{
    std::cout << "void print(int num)" << std::endl;
}

// 函数重载 - 参数顺序不同
void print(int num, const std::string& str)
{
    std::cout << "void print(int num, const std::string& str)" << std::endl;
}

// 参数名称不同
void print(const std::string& str, int num) 
{
    std::cout << "void print(const std::string& str, int num)" << std::endl;
}

int main() 
{
    print("Hello, World!");
    print("Hello, World!", 3);
    print(42);
    print(42, "Hello, World!");
    print("Hello, World!", 42);
    return 0;
}

C++ 模板

简介: C++ 模板是一种泛型编程技术,允许在编译时根据实际类型生成函数和类的特定版本。

详细介绍: C++ 模板具有以下特点:

  • 使用 template 关键字定义函数模板和类模板
  • 支持模板参数(类型参数和非类型参数)
  • 使用模板特化自定义模板的特定版本

应用场景: C++ 模板适用于需要实现通用函数和类的项目,提高代码的复用性和灵活性。

语法详细代码:

#include <iostream>

// 函数模板定义 typename 是关键字,用于代表一个模板类型
template <typename T>
T max(T a, T b)
{
    return (a > b) ? a : b;
}

// 类模板定义 class 也代表类型
template <class T>
class Stack {
private:
    T data[10];
    int top;

public:
    Stack() : top(-1) {}

    void push(T item) 
    {
        if (top == 9)
        {
            std::cerr << "Stack is full." << std::endl;
            return;
        }
        data[++top] = item;
    }

    T pop()
    {
        if (top == -1)
        {
            std::cerr << "Stack is empty." << std::endl;
            return T();
        }
        return data[top--];
    }
};

int main() 
{
    // 使用函数模板
    std::cout << "Max(3, 4) = " << max(3, 4) << std::endl;
    std::cout << "Max(3.1, 4.2) = " << max(3.1, 4.2) << std::endl;

    // 使用类模板
    Stack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    std::cout << "Pop: " << intStack.pop() << std::endl;

    return 0;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐