C++ 系列 -- 基础知识

3,729 阅读41分钟

数据类型

七种基本的 C++ 数据类型

类型关键字
布尔型bool
字符型char
整型int
浮点型float
双浮点型double
无类型void
宽字符型wchar_t

一些基本类型可以使用一个或多个类型修饰符进行修饰:

  • signed:表示变量可以存储负数。对于整型变量来说,signed 可以省略,因为整型变量默认为有符号类型
  • unsigned:表示变量不能存储负数。对于整型变量来说,unsigned 可以将变量范围扩大一倍
  • short:表示变量的范围比 int 更小。short int 可以缩写为 short
  • long:表示变量的范围比 int 更大。long int 可以缩写为 long
  • long long:表示变量的范围比 long 更大。C++11 中新增的数据类型修饰符

各数据类型的内存和范围

下表显示了各种数据类型在内存中存储值时需要占用的内存,以及该类型的变量所能存储的最大值和最小值

类型范围
char1 个字节-128 到 127 或者 0 到 255
unsigned char1 个字节0 到 255
signed char1 个字节-128 到 127
int4 个字节-2147483648 到 2147483647
unsigned int4 个字节0 到 4294967295
signed int4 个字节-2147483648 到 2147483647
short int2 个字节-32768 到 32767
unsigned short int2 个字节0 到 65,535
signed short int2 个字节-32768 到 32767
long int8 个字节-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
signed long int8 个字节-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long int8 个字节0 到 18,446,744,073,709,551,615
float4 个字节精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字)
double8 个字节双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字)
long double16 个字节长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字
wchar_t2 或 4 个字节1 个宽字符

数据类型的声明

bool isRight = true;
int age = 5;
void sum() {

}

auto 关键字:自动推导类型声明

auto 关键字在 C++11 中引入,用于自动类型推导。当使用 auto 关键字声明变量时,编译器会根据变量的初始值自动推导出变量的类型。这可以简化代码,避免显式地指定复杂类型

(类似于 C# 和 JavaScript 中的 var 关键字)

int a = 42;
auto b = a; // 类型推导为 int

unordered_map<int, int> hashtable; // 一个 int-int 的哈希表
auto item = hashtable.find(target - nums[i]); // 用 auto 关键字之后不需要显式的判断 item 的类型

auto 关键字在 C++ 中非常有用,可以用于简化变量声明,特别是在处理复杂类型(如迭代器、模板类型等)时。例如,当使用迭代器遍历容器时,可以使用 auto 关键字来简化迭代器的声明

枚举类型

例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 "blue"

enum color { red, green, blue } c;
c = blue;

类型判断

  1. typeid 运算符:可以用于获取一个对象的类型信息,返回一个 type_info 对象(类型的首字母)。例如:

    int i = 42;
    float j = 42.5f;
    std::cout << typeid(i).name() << std::endl;  // 输出:i
    std::cout << typeid(j).name() << std::endl;  // 输出:f
    
  2. std::is_same 类型特征:可以用于检查两种类型是否相同。例如:

    std::cout << std::is_same<int, float>::value << std::endl;  // 输出:0
    std::cout << std::is_same<int, int>::value << std::endl;    // 输出:1
    
  3. std::is_integral 类型特征:可以用于检查一个类型是否为整型。例如:

    std::cout << std::is_integral<int>::value << std::endl;    // 输出:1
    std::cout << std::is_integral<float>::value << std::endl;  // 输出:0
    
  4. std::is_floating_point 类型特征:可以用于检查一个类型是否为浮点型。例如:

    std::cout << std::is_floating_point<int>::value << std::endl;    // 输出:0
    std::cout << std::is_floating_point<float>::value << std::endl;  // 输出:1
    
  5. std::is_pointer 类型特征:可以用于检查一个类型是否为指针类型。例如:

    std::cout << std::is_pointer<int*>::value << std::endl;    // 输出:1
    std::cout << std::is_pointer<float>::value << std::endl;  // 输出:0
    
  6. std::is_array 类型特征:可以用于检查一个类型是否为数组类型。例如:

    std::cout << std::is_array<int[]>::value << std::endl;    // 输出:1
    std::cout << std::is_array<float>::value << std::endl;    // 输出:0
    
  7. std::is_function 类型特征:可以用于检查一个类型是否为函数类型。例如:

    std::cout << std::is_function<int(int)>::value << std::endl;  // 输出:1
    std::cout << std::is_function<float>::value << std::endl;     // 输出:0
    

这些类型判断方式可以帮助我们在编写泛型代码时更加灵活和安全

类型转换

隐式类型转换

简单粗暴,但是存在问题。例如将一个 float 类型的值 42.5 转换为 int 类型,由于 int 类型不支持小数部分,因此在进行转换时,小数部分会被截断,只保留整数部分

float f = 42.5f;
int i = int(f);
int i = (int)f;
int i = f;

显式类型转换

C 风格的类型转换:使用括号将需要转换的类型括起来,并在前面添加需要转换的类型。例如:

int i = 42;
float f = (float)i;  // C 风格的类型转换

C++ 风格的类型转换:使用 static_castdynamic_castconst_cast 或 reinterpret_cast 进行类型转换。例如:

静态转换static_cast是将一种数据类型的值强制转换为另一种数据类型的值

int i = 42;
float f = static_cast<float>(i);

动态转换dynamic_cast通常用于将一个基类指针或引用转换为派生类指针或引用

class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针

常量转换const_cast用于将 const 类型的对象转换为非 const 类型的对象

const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int

重新解释转换reinterpret_cast将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换

int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型

数字和字符串互转

  • 字符串转 int、float
#include <iostream>
#include <string>

int main() {
    std::string str = "42";
    int i = std::stoi(str);
    std::cout << "The integer is: " << i << std::endl;
    float f = std::stof(str);
    std::cout << "The float is: " << f << std::endl;
}

这些函数都需要包含头文件 <string>,接受一个字符串作为参数。如果字符串不包含有效的数字,则会抛出 std::invalid_argument 或 std::out_of_range 异常。

需要注意的是,这些函数只能将符合特定格式的字符串转换为数字类型。例如,对于 std::stoi,字符串必须以数字开头,可以包含正负号,但不能包含其他字符。对于 std::stof,字符串必须包含小数点和数字,可以包含正负号和指数符号,但不能包含其他字符

  • int、float 转字符串
#include <iostream>
#include <string>

int main() {
    int i = 42;
    float f = 3.14;
    std::string str1 = std::to_string(i);
    std::string str2 = std::to_string(f);
    std::cout << "The integer string is: " << str1 << std::endl;
    std::cout << "The float string is: " << str2 << std::endl;
}

常量

在 C++ 中,有两种简单的定义常量的方式:

  • 使用 #define 预处理器。
  • 使用 const 关键字。

define 预处理器

下面是使用 #define 预处理器定义常量的形式:

#define identifier value

具体请看下面的实例:

#include <iostream>
using namespace std;
 
#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'
 
int main()
{
 
   int area;
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
}

const 关键字

您可以使用 const 前缀声明指定类型的常量,如下所示:

const type variable = value;

具体请看下面的实例:

#include <iostream>
using namespace std;
 
int main()
{
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
}

类型限定符

类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。

限定符含义
constconst 定义常量,表示该变量的值不能被修改。。
volatile修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程
restrict由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict
mutable表示类中的成员变量可以在 const 成员函数中被修改
static用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问
register用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。

const 实例

const int NUM = 10; // 定义常量 NUM,其值不可修改
const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改
int const* ptr2 = &NUM; // 和上面一行等价

volatile 实例

volatile int num = 20; // 定义变量 num,其值可能会在未知的时间被改变

mutable 实例

class Example {
public:
    int get_value() const {
        return value_; // const 关键字表示该成员函数不会修改对象中的数据成员
    }
    void set_value(int value) const {
        value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量
    }
private:
    mutable int value_;
};

static 实例

void example_function() {
    static int count = 0; // static 关键字使变量 count 存储在程序生命周期内都存在
    count++;
}

register 实例

void example_function(register int num) {
    // register 关键字建议编译器将变量 num 存储在寄存器中
    // 以提高程序执行速度
    // 但是实际上是否会存储在寄存器中由编译器决定
}

变量作用域

有三个地方可以定义变量:

  • 在函数或一个代码块内部声明的变量,称为局部变量
  • 在函数参数的定义中声明的变量,称为形式参数
  • 在所有函数外部声明的变量,称为全局变量

局部变量和全局变量

例子:

#include <iostream>
using namespace std;

int g; // 全局变量声明
int main ()
{
  int a, b; // 局部变量声明
  // 实际初始化
  a = 10;
  b = 20;
  g = a + b;
  cout << g;
}

局部变量的值会覆盖全局变量的值,例子:

#include <iostream>
using namespace std;

int g; // 全局变量声明
int main ()
{
  int g = 10; // 局部变量声明
  cout << g;
}

变量的作用域可以分为以下几种:

  • 局部作用域:在函数内部声明的变量具有局部作用域,它们只能在函数内部访问。局部变量在函数每次被调用时被创建,在函数执行完后被销毁
  • 全局作用域:在所有函数和代码块之外声明的变量具有全局作用域,它们可以被程序中的任何函数访问。全局变量在程序开始时被创建,在程序结束时被销毁
  • 块作用域:在代码块内部声明的变量具有块作用域,它们只能在代码块内部访问。块作用域变量在代码块每次被执行时被创建,在代码块执行完后被销毁
  • 类作用域:在类内部声明的变量具有类作用域,它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同

块作用域

#include <iostream>

int main() {
    int a = 10;
    {
        int a = 20;  // 块作用域变量
        std::cout << "块变量: " << a << std::endl;
    }
    std::cout << "外部变量: " << a << std::endl;
}

// 输出结果
块变量: 20
外部变量: 10

类作用域

可以使用类名和作用域解析运算符 :: 来访问这个变量

#include <iostream>

class MyClass {
public:
    static int class_var;  // 类作用域变量
};
int MyClass::class_var = 30; // 使用类名和作用域解析运算符 :: 来访问这个变量
int main() {
    std::cout << "类变量: " << MyClass::class_var << std::endl;
}

C++ 内存分区

  1. 全局区Global

全局区是存放全局变量静态变量的内存区域,在程序启动时自动分配,在程序结束时自动释放。全局区的内存空间是连续的,由编译器自动管理。全局区的大小也是固定的,因此只能存放较小的数据

  1. 常量区Const

常量区是存放常量数据的内存区域,如字符串常量、全局常量等。常量区内存只读,不可修改。常量区的内存空间是连续的,由编译器自动管理

  1. 栈区Stack

栈区是由编译器自动分配和释放的内存区域,存放函数的参数值、局部变量等。栈区内存的分配和释放速度很快,因为它的内存空间是连续的,且由编译器自动管理。栈区的大小是固定的,一般只能存放较小的数据。当函数执行完毕后,栈区内存会自动释放,因此不需要手动释放栈区内存

  1. 堆区Heap

堆区是由程序员手动分配和释放的内存区域,存放程序运行期间动态分配的内存。堆区的内存空间是不连续的,因此内存分配和释放的速度较慢,但是堆区的内存空间较大,可以存放较大的数据。堆区内存的分配和释放需要使用 new 和 delete 或 malloc 和 free 等函数手动管理

  1. 代码区Code

代码区是存放程序的可执行代码的内存区域,由操作系统负责管理。代码区的内存空间是只读的,不可修改

运算符

算术运算符

下表显示了 C++ 支持的算术运算符。

假设变量 A 的值为 10,变量 B 的值为 21,则:

运算符描述实例
+把两个操作数相加A + B 将得到 31
-从第一个操作数中减去第二个操作数A - B 将得到 -11
*把两个操作数相乘A * B 将得到 210
/取整B / A 将得到 2
%取余B % A 将得到 1
++自增运算符,整数值增加 1A++ 将得到 11
--自减运算符,整数值减少 1A-- 将得到 9

关系运算符

下表显示了 C++ 支持的关系运算符。

假设变量 A 的值为 10,变量 B 的值为 20,则:

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 不为真。
!=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 不为真。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 不为真。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

逻辑运算符

下表显示了 C++ 支持的关系逻辑运算符。

假设变量 A 的值为 1,变量 B 的值为 0,则:

运算符描述实例
&&称为逻辑与运算符。如果两个操作数都 true,则条件为 true。(A && B) 为 false。
||称为逻辑或运算符。如果两个操作数中有任意一个 true,则条件为 true。(A || B) 为 true。
!称为逻辑非运算符。用来逆转操作数的逻辑状态,如果条件为 true 则逻辑非运算符将使其为 false。!(A && B) 为 true。

其他一些重要的运算符

运算符描述
sizeofsizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。
Condition ? X : Y条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。
,(逗号运算符)逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。
.(点运算符)和 ->(箭头运算符)成员运算符用于引用类、结构和共用体的成员
::(双冒号运算符)用于直接访问命名空间、类、结构体、共用体或枚举类型的成员或静态成员
Cast(强制转换运算符)强制类型转换,把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2
&(指针运算符)指针运算符 & 由变量获取到它的地址。例如 &a 得到变量 a 的地址
*(指针运算符)指针运算符 * 由地址获取到变量的值。例如 *ptr 得到地址 ptr 指向的变量

逗号运算符(,)

  • 使用逗号运算符是为了把几个表达式放在一起
  • 整个逗号表达式的值为系列中最后一个表达式的值

例子:

#include <iostream>
using namespace std;
 
int main()
{
   int i, j;
   j = 10;
   i = (j++, j+100, 999+j);
   cout << i; // 1010
}

成员运算符(. ->)

成员运算符用于引用类、结构和共用体的(public)成员

class Point {
public:
    int x;
    int y;
    void print() {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};

int main() {
    /** 点运算符 */
    Point p;
    p.x = 5;  // 访问成员变量
    p.print();  // 访问成员函数
    
    /* 箭头运算符 */
    Point* pPtr = new Point();
    pPtr->x = 5;  // 访问成员变量
    pPtr->print();  // 访问成员函数
    delete pPtr;
}

双冒号运算符(::)

双冒号运算符用于访问命名空间、枚举类型、类的成员

以下例子中演示访问命名空间 NS 中的成员变量 x,其实 std 也是一个命名空间,其包含了 C++ 标准库中的所有标识符,例如标准输入输出流、容器、算法等等

/* 命名空间 */
namespace NS {
    int x = 42;
}
/* 类 */
class MyClass {
public:
    static int y;
};
/* 枚举类型 */
enum Color { RED, GREEN, BLUE };

int MyClass::y = 123; // 访问类的静态成员

int main() {
    std::cout << NS::x << std::endl;  // 访问命名空间成员
    std::cout << MyClass::y << std::endl;  // 访问类的静态成员
    std::cout << Color::RED << std::endl;  // 访问枚举类型成员
}

基类也是类的一种,所以访问基类的成员也用双冒号运算符

class Base {
public:
    int x;
    void print() {
        std::cout << "Base::print()" << x << std::endl;
    }
};

class Derived : public Base {
public:
    void print() {
        Base::print();  // 调用基类的方法
        std::cout << "Derived::print()" << Base::x << std::endl;  // 访问基类的成员
    }
};

int main() {
    Derived d;
    d.x = 42;
    d.print();
}

指针运算符(& *)

#include <iostream>
using namespace std;
 
int main ()
{
   int  var;
   int  *ptr; // * 运算符也可以用来表示一个指针
   int  val;

   var = 3000;

   ptr = &var; // 获取变量 var 的地址,赋值给 ptr
   val = *ptr; // 获取地址 ptr 指向的变量 var 的值
   cout << "Value of var :" << var << endl;
   cout << "Value of ptr :" << ptr << endl;
   cout << "Value of val :" << val << endl;

   return 0;
}

注释

C++ 存在三种注释:

  • //  一般用于单行注释
  • /* ... */  一般用于多行注释
  • #if 0 ... #endif 条件编译注释
#include <iostream>
using namespace std;
 
int main() {
    // 这是单行注释
    
    /* 这是注释 */
    /* 
     * 可以多行注释
     */ 
    cout << "Hello World!";
    return 0;
}

块注释用于程序调试,测试时使用  #if 1 来执行测试代码,发布后使用  #if 0 来屏蔽测试代码

#if condition
  code1
#else
  code2
#endif

基本的输入和输出

I/O 库头文件

下列的头文件在 C++ 编程中很重要。

头文件函数和描述
<iostream>该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
<iomanip>该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。
<fstream>该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。

标准输出流(cout)

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Hello C++";
   cout << "Value of str is : " << str << endl;
}

C++ 编译器根据要输出变量的数据类型,选择合适的流插入运算符来显示值。<< 运算符被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项。

流插入运算符 << 在一个语句中可以多次使用,如上面实例中所示,endl 用于在行末添加一个换行符

标准输入流(cin)

#include <iostream>
using namespace std;
 
int main( )
{
   char name[50];
   cout << "请输入您的名称: ";
   cin >> name;
   cout << "您的名称是: " << name << endl;
}

流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:

cin >> name >> age;

这相当于下面两个语句:

cin >> name;
cin >> age;

标准错误流(cerr)

预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准输出设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Unable to read....";
   cerr << "Error message : " << str << endl;
}

标准日志流(clog)

预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准输出设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Unable to read....";
   clog << "Error message : " << str << endl;
}

日期和时间

C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。

有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。

结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:

struct tm {
  int tm_sec;   // 秒,正常范围从 0 到 59,但允许至 61
  int tm_min;   // 分,范围从 0 到 59
  int tm_hour;  // 小时,范围从 0 到 23
  int tm_mday;  // 一月中的第几天,范围从 1 到 31
  int tm_mon;   // 月,范围从 0 到 11
  int tm_year;  // 自 1900 年起的年数
  int tm_wday;  // 一周中的第几天,范围从 0 到 6,从星期日算起
  int tm_yday;  // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
  int tm_isdst; // 夏令时
};

下面是 C/C++ 中关于日期和时间的重要函数。所有这些函数都是 C/C++ 标准库的组成部分,您可以在 C++ 标准库中查看一下各个函数的细节。

函数描述
time_t time(time_t *time);该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 -1
char *ctime(const time_t *time);该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\0
struct tm *localtime(const time_t *time);该函数返回一个指向表示本地时间的 tm 结构的指针
clock_t clock(void);该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 -1
char * asctime ( const struct tm * time );该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0。
struct tm *gmtime(const time_t *time);该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示
time_t mktime(struct tm *time);该函数返回日历时间,相当于 time 所指向结构中存储的时间
double difftime ( time_t time2, time_t time1 );该函数返回 time1 和 time2 之间相差的秒数
size_t strftime();该函数可用于格式化日期和时间为指定的格式

当前日期和时间

下面的实例获取当前系统的日期和时间,包括本地时间和协调世界时(UTC)

#include <iostream>
#include <ctime>
using namespace std;
 
int main()
{
   // 基于当前系统的当前日期/时间
   time_t now = time(0);
   
   // 把 now 转换为字符串形式
   char* dt = ctime(&now);
 
   cout << "本地日期和时间:" << dt << endl;
 
   // 把 now 转换为 tm 结构
   tm *gmtm = gmtime(&now);
   dt = asctime(gmtm);
   cout << "UTC 日期和时间:"<< dt << endl;
}

输出结果:
本地日期和时间:Fri Sep 15 06:44:51 2023
UTC 日期和时间:Fri Sep 15 06:44:51 2023

使用结构 tm 格式化时间

C 库函数 size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr  根据 format 中定义的格式化规则,格式化结构 timeptr 表示的时间,并把它存储在 str 中。

下面是 strftime() 函数的声明

size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
  • str -- 这是指向目标数组的指针,用来复制产生的 C 字符串。
  • maxsize -- 这是被复制到 str 的最大字符数。
  • format -- 这是 C 字符串,包含了普通字符和特殊格式说明符的任何组合。这些格式说明符由函数替换为表示 tm 中所指定时间的相对应值。格式说明符是:
#include <stdio.h>
#include <time.h>
 
int main ()
{
   time_t rawtime;
   struct tm *info;
   char buffer[80];
   time( &rawtime );
   info = localtime( &rawtime );
   strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
   printf("格式化的日期 & 时间 : %s\n", buffer );
}

输出结果:
格式化的日期 & 时间 : 2023-09-15 06:45:54

判断

判断语句

C++ 编程语言提供了以下类型的判断语句。点击链接查看每个语句的细节。

语句描述
if 语句一个 if 语句 由一个布尔表达式后跟一个或多个语句组成
if...else 语句一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行
嵌套 if 语句您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句
switch 语句一个 switch 语句允许测试一个变量等于多个值时的情况
嵌套 switch 语句您可以在一个 switch 语句内使用另一个 switch 语句

? : 运算符

我们已经在前面的章节中讲解了 条件运算符 ? : ,可以用来替代 if...else 语句。它的一般形式如下:

Exp1 ? Exp2 : Exp3;

其中,Exp1、Exp2 和 Exp3 是表达式。请注意,冒号的使用和位置

? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值

循环

循环类型

C++ 编程语言提供了以下几种循环类型。点击链接查看每个类型的细节。

循环类型描述
while 循环当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件
for 循环多次执行一个语句序列,简化管理循环变量的代码
do...while 循环除了它是在循环主体结尾测试条件外,其他与 while 语句类似
嵌套循环您可以在 while、for 或 do..while 循环内使用一个或多个循环

循环控制语句

循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。

C++ 提供了下列的控制语句。点击链接查看每个语句的细节。

控制语句描述
break 语句终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句
continue 语句引起循环跳过主体的剩余部分,立即重新开始测试条件
goto 语句将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句

字符串

C++ 标准库提供了 string 类类型,需要引入 #include <string>

1. 构造字符串

string s1();  // si = ""
string s2("Hello");  // s2 = "Hello"
string s3(4, 'K');  // s3 = "KKKK"
string s4("12345", 1, 3);  //s4 = "234",即 "12345" 的从下标 1 开始,长度为 3 的子串

注意:string 类不接收一个整型参数或一个字符型参数的构造函数。下面的两种写法是错误的

string s1('K'); // 不接收一个字符型参数
string s2(123); // 不接收一个整型参数

2. 求字符串长度

string s1 = "hello world";
cout << s1.length() << endl; // 11
cout << s1.size() << endl; // 11

3. 字符串拼接

除了可以使用++=运算符对 string 对象执行字符串的连接操作外,string 类还有 append 成员函数,可以用来向字符串后面添加内容。append 成员函数返回对象自身的引用,会改变原字符串。例如:

string s1("123"), s2("abc");
s1.append(s2);  // s1 = "123abc"
s1.append(s2, 1, 2);  // s1 = "123abcbc"
s1.append(3, 'K');  // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3);  // s1 = "123abcbcKKKCDE",添加 "ABCDE" 的子串(2, 3)

补充见 c.biancheng.net/view/400.ht…

指针

什么是内存地址

每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示这个变量在内存中的地址

#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var1;
   char var2[10];
 
   cout << "var1 变量的地址: ";
   cout << &var1 << endl;
 
   cout << "var2 变量的地址: ";
   cout << &var2 << endl;
 
   return 0;
}

运行结果:

var1 变量的地址: 0xbfebd5c0
var2 变量的地址: 0xbfebd5b6

什么是指针

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同

指针的使用

指针的使用一般包括:

  • 定义一个指针变量
  • 把变量地址赋值给指针
  • 访问指针变量中可用地址的值
#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var = 20;   // 实际变量的声明
   int  *ip;        // 指针变量的声明
 
   ip = &var;       // 在指针变量中存储 var 的地址
 
   cout << "变量 var 的值是:";
   cout << var << endl;
 
   // 输出在指针变量中存储的地址
   cout << "指针 ip 指向的地址是:";
   cout << ip << endl;
 
   // 访问指针中地址的值
   cout << "指针 ip 指向的地址存的变量的值是:";
   cout << *ip << endl;
 
   return 0;
}

运行结果:

变量 var 的值是:20
指针 ip 指向的地址是:0xbfc601ac
指针 ip 指向的地址存的变量的值是:20

空指针

赋为 NULL 值的指针被称为空指针

#include <iostream>

using namespace std;

int main ()
{
   int  *ptr = NULL;
   cout << "ptr 的值是 " << ptr ;
   return 0;
}

运行结果:

ptr 的值是 0

内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

如需检查一个空指针,您可以使用 if 语句,如下所示:

if(ptr)     /* 如果 ptr 非空,则完成 */
if(!ptr)    /* 如果 ptr 为空,则完成 */

因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。

指针自增自减比较

指针自增自减比较通常用于数组

指针递增

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int  *ptr;
 
   ptr = var; // 指针中的数组地址
   // ptr = &var[MAX-1]; // 指针中最后一个元素的地址
   for (int i = 0; i < MAX; i++)
   {
      cout << "Address of var[" << i << "] = ";
      cout << ptr << endl;
 
      cout << "Value of var[" << i << "] = ";
      cout << *ptr << endl;
 
      ptr++; // 移动到下一个位置
   }
   return 0;
}

运行结果:

Address of var[0] = 0xbfa088b0
Value of var[0] = 10
Address of var[1] = 0xbfa088b4
Value of var[1] = 100
Address of var[2] = 0xbfa088b8
Value of var[2] = 200

指针比较

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int  *ptr;
   
   ptr = var; // 指针中第一个元素的地址
   int i = 0;
   while ( ptr <= &var[MAX - 1] ) // 指针指向是否在数组范围内
   {
      cout << "Address of var[" << i << "] = ";
      cout << ptr << endl;
 
      cout << "Value of var[" << i << "] = ";
      cout << *ptr << endl;
 
      // 指向上一个位置
      ptr++;
      i++;
   }
   return 0;
}

运行结果:

Address of var[0] = 0xbfce42d0
Value of var[0] = 10
Address of var[1] = 0xbfce42d4
Value of var[1] = 100
Address of var[2] = 0xbfce42d8
Value of var[2] = 200

指针数组

在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int *ptr[MAX];
 
   for (int i = 0; i < MAX; i++)
   {
      ptr[i] = &var[i]; // 赋值为整数的地址
   }
   for (int i = 0; i < MAX; i++)
   {
      cout << "Value of var[" << i << "] = ";
      cout << *ptr[i] << endl;
   }
   return 0;
}

运行结果:

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

也可以用一个指向字符的指针数组来存储一个字符串列表,如下:

#include <iostream>
 
using namespace std;
const int MAX = 4;
 
int main ()
{
 const char *names[MAX] = {
                   "Zara Ali",
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
 
   for (int i = 0; i < MAX; i++)
   {
      cout << "Value of names[" << i << "] = ";
      cout << names[i] << endl;
   }
   return 0;
}

运行结果:

Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali

指向指针的指针(多级间接寻址)

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链

指针的指针就是将指针的地址存放在另一个指针里面

通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

image.png

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

#include <iostream>
 
using namespace std;
 
int main ()
{
    int  var;
    int  *ptr;
    int  **pptr;
 
    var = 3000;
 
    // 获取 var 的地址
    ptr = &var;
 
    // 使用运算符 & 获取 ptr 的地址
    pptr = &ptr;
 
    // 使用 pptr 获取值
    cout << "var 值为 :" << var << endl;
    cout << "*ptr 值为:" << *ptr << endl;
    cout << "**pptr 值为:" << **pptr << endl;
 
    return 0;
}

运行结果:

var 值为 :3000
*ptr 值为:3000
**pptr 值为:3000

多级间接寻址的应用场景

  1. 动态内存分配:使用多级指针可以实现动态内存分配,即分配一个指针数组,用于存储多个指针变量的地址,然后再分配每个指针变量所指向的内存空间。
  2. 多维数组:在多维数组中,可以使用多级指针来表示二维、三维等多维数组,以便在程序中动态地分配和访问多维数组。
  3. 数据结构:在数据结构中,可以使用多级指针来表示链表、树等数据结构,以便在程序中动态地添加、删除和修改节点。
  4. 函数指针:在函数指针中,可以使用多级指针来表示指向函数指针的指针,以便在程序中动态地管理函数指针

传递指针给函数

C++ 允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可

下面的实例中,我们传递一个无符号的 long 型指针给函数,并在函数内改变这个值:

#include <iostream>
#include <ctime>
 
using namespace std;
 
// 在写函数时应习惯性的先声明函数,然后在定义函数
void getSeconds(unsigned long *par);
 
int main ()
{
   unsigned long sec;
   getSeconds( &sec );
   cout << "Number of seconds :" << sec << endl // 输出实际值
 
   return 0;
}
 
void getSeconds(unsigned long *par)
{
   *par = time( NULL ); // 获取当前的秒数
   return;
}

当上面的代码被编译和执行时,它会产生下列结果:

Number of seconds : 1695175690

能接受指针作为参数的函数,也能接受数组作为参数,如下所示:

#include <iostream>
using namespace std;
 
double getAverage(int *arr, int size); // 函数声明
 
int main ()
{
   // 带有 5 个元素的整型数组
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
   avg = getAverage( balance, 5 ); // 传递一个指向数组的指针作为参数
   cout << "Average value is: " << avg << endl; // 输出返回值
    
   return 0;
}
 
double getAverage(int *arr, int size)
{
  int  i, sum = 0;       
  double avg;
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
  avg = double(sum) / size;
  return avg;
}

当上面的代码被编译和执行时,它会产生下列结果:

Average value is: 214.4

从函数返回指针

C++ 只支持在函数外返回 static 类型的局部变量的地址(因为如果不是 static 类型的变量,函数执行结束后该地址指向的局部变量就被销毁了,返回出来的这个指针就没有意义了)

现在,让我们来看下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:

#include <iostream>
#include <ctime>
#include <cstdlib>
 
using namespace std;
 
// 要生成和返回随机数的函数
int * getRandom( )
{
  static int  r[5];
 
  srand( (unsigned)time( NULL ) ); // 设置种子
  for (int i = 0; i < 5; ++i)
  {
    r[i] = rand();
    cout << r[i] << endl;
  }
 
  return r;
}
 
// 要调用上面定义函数的主函数
int main ()
{
   int *p; // 一个指向整数的指针
 
   p = getRandom();
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
 
   return 0;
}

运行结果:

624723190
1468735695
807113585
976495677
613357504
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504

引用

引用的本质是一个已存在变量别名

创建引用

int i = 17;

int& r = i; // 成功创建 i 的引用变量 r

r 可以读作是"一个初始化为 i 的整型引用"

#include <iostream>
 
using namespace std;
 
int main ()
{
   // 声明简单的变量
   int    i;
   double d;
 
   // 声明引用变量
   int&    r = i;
   double& s = d;
   
   i = 5;
   cout << "Value of i : " << i << endl;
   cout << "Value of i reference : " << r  << endl;
 
   d = 11.7;
   cout << "Value of d : " << d << endl;
   cout << "Value of d reference : " << s  << endl;
   
   return 0;
}

运行结果:

Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7

把引用作为参数

#include <iostream>
using namespace std;
 
// 函数声明
void swap(int& x, int& y);
 
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
 
// 函数定义
void swap(int& x, int& y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
  
   return;
}

把引用作为返回值

通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。C++ 函数可以返回一个引用,方式与返回一个指针类似。

当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。例如,请看下面这个简单的程序:

#include <iostream>
 
using namespace std;
 
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
 
double& setValues(int i) {  
   double& ref = vals[i];    
   return ref;   // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
 
 
}
 
// 要调用上面定义函数的主函数
int main ()
{
 
   cout << "改变前的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }
 
   setValues(1) = 20.23; // 改变第 2 个元素
   setValues(3) = 70.8;  // 改变第 4 个元素
 
   cout << "改变后的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }
   return 0;
}

运行结果:

改变前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改变后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50

当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

int& func() {
   int q;
   //! return q; // 在编译时发生错误
   static int x;
   return x;     // 安全,x 在函数作用域外依然是有效的
}

引用和指针的区别

五个主要的不同:

  1. 初始化:引用必须在定义时进行初始化,指针可以在任何时候进行初始化
  2. 空值:引用不能为空,必须引用某个已经存在的变量,指针可以为空
  3. 重定义:引用不能被重新定义,即不能让引用引用另一个变量,指针可以被重新定义
  4. 运算符:引用没有自己的运算符,它使用被引用变量的运算符,指针有自己的运算符,如 * 和 ->
  5. 内存管理:引用不需要进行内存管理,它不会导致内存泄漏和野指针等问题,指针需要进行内存管理,需要手动分配和释放内存

引用和指针的应用场景

  1. 函数参数传递:在函数参数传递中,如果需要修改参数的值,则可以使用指针或引用类型来传递参数。如果参数可以为空,则可以使用指针类型。如果参数不能为空,则可以使用引用类型。例如:
// 使用指针传递参数
void func(int* p) {
    if(p){
        *p = 10;
    }
}

// 使用引用传递参数
void func(int& r) {
    r = 10;
}
  1. 动态内存分配:在动态内存分配中,需要使用指针类型来存储分配的内存地址,以便后续访问内存中的数据。例如:
// 动态分配内存,返回指针
int* p = new int[10];

// 访问内存中的数据
p[0] = 1;
  1. 数据结构:在数据结构中,需要使用指针类型来表示数据结构中的节点,以便在程序中动态地添加、删除和修改节点。例如:
// 定义链表节点
struct Node {
    int val;
    Node* next;
};

// 动态分配节点,返回指针
Node* p = new Node;

// 修改节点的值和指针
p->val = 1;
p->next = nullptr;
  1. 运算符重载:在运算符重载中,需要使用引用类型来实现运算符的重载,以便更加直观和安全地访问数据。例如:
// 定义向量类
class Vector {
public:
    // 重载下标运算符
    int& operator[](int i) {
        return data[i];
    }

private:
    int data[10];
};

// 使用引用访问向量元素
Vector v;
v[0] = 1;

数组

数组声明

必须声明数组元素类型、数组变量名、数组大小

type arrayName [ arraySize ];

如:
double balance[10];

数组初始化

采用花括号 {} 来包含数组

  • 数组大小必须大于等于元素个数
  • 数组大小不填则默认是元素个数
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

数组元素访问

double salary = balance[9];

多维数组

初始化

int a[3][4] = {  
 {0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
 {4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
 {8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};

访问

int val = a[2][3];

指向数组的指针

double *p;
double runoobAarray[10];

p = runoobAarray;

p = runoobAarray; 表示把第一个元素的地址存储在 p 中,接下来就可以使用 *p*(p+1)*(p+2) 等来访问数组元素

#include <iostream>
using namespace std;
 
int main ()
{
   // 带有 5 个元素的双精度浮点型数组
   double runoobAarray[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
   double *p;
 
   p = runoobAarray;
 
   // 输出数组中每个元素的值
   cout << "使用指针的数组值 " << endl; 
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
 
   cout << "使用 runoobAarray 作为地址的数组值 " << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(runoobAarray + " << i << ") : ";
       cout << *(runoobAarray + i) << endl;
   }
 
   return 0;
}

运行结果:

使用指针的数组值 
*(p + 0) : 1000
*(p + 1) : 2
*(p + 2) : 3.4
*(p + 3) : 17
*(p + 4) : 50
使用 runoobAarray 作为地址的数组值 
*(runoobAarray + 0) : 1000
*(runoobAarray + 1) : 2
*(runoobAarray + 2) : 3.4
*(runoobAarray + 3) : 17
*(runoobAarray + 4) : 50

传递数组给函数

三种函数形式参数的声明方式:

// 形式参数是一个指针:
void myFunction(int *param)
{

}
// 形式参数是一个已定义大小的数组:
void myFunction(int param[10])
{

}
// 形式参数是一个未定义大小的数组:
void myFunction(int param[])
{

}

例子:

#include <iostream>
using namespace std;

double getAverage(int arr[], int size); // 函数声明

int main ()
{
   int balance[5] = {1000, 2, 3, 17, 50}; // 带有 5 个元素的整型数组
   double avg;
   avg = getAverage( balance, 5 ); // 传递一个指向数组的指针作为参数
   cout << "平均值是:" << avg << endl;  // 输出返回值
   return 0;
}

double getAverage(int arr[], int size)
{
  int i, sum = 0;       
  double avg;
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
  avg = double(sum) / size;
  return avg;
}

从函数返回数组

由于在函数内部定义的数组属于局部变量,函数执行完后这个局部数组的内存会被释放,返回出来的指针指向的数组已经不存在了,所以不能用局部数组。只能选择用静态数组动态分配数组

静态数组

静态数组就是前面加个 static

使用静态数组需要在函数内部创建一个静态数组,并将其地址返回,例如:

int* myFunction()
{
   static int myArray[3] = {1, 2, 3};
   return myArray;
}

让我们来看下面的函数,它会生成 10 个随机数,并使用数组来返回它们

#include <iostream>
#include <cstdlib>
#include <ctime>
 
using namespace std;
 
// 要生成和返回随机数的函数
int * getRandom( )
{
  static int  r[5];
 
  // 设置种子
  srand( (unsigned)time( NULL ) );
  for (int i = 0; i < 5; ++i)
  {
    r[i] = rand();
    cout << r[i] << endl;
  }
 
  return r;
}
 
// 要调用上面定义函数的主函数
int main ()
{
   // 一个指向整数的指针
   int *p;
 
   p = getRandom();
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
 
   return 0;
}

运行结果:

624723190
1468735695
807113585
976495677
613357504
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504

动态分配数组

在函数内部动态分配的数组在函数执行结束时不会自动释放,所以需要调用函数的代码负责释放返回的数组

#include <iostream>
using namespace std;

int* createArray(int size) {
    int* arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

int main() {
    int* myArray = createArray(5); // 调用返回数组的函数,得到一个指向数组的指针
    for (int i = 0; i < 5; i++) {
        cout << myArray[i] << " ";
    }
    cout << endl;
    delete[] myArray; // 释放内存
    return 0;
}

运行结果:

1 2 3 4 5 

参考内容