本文最后更新于 2024-10-18,文章内容距离上一次更新已经过去了很久啦,可能已经过时了,请谨慎参考喵。

title: Cpp学习笔记之四
top_img: false
tags:
  - C++
categories:
  - 编程语言
cover: false
abbrlink: abd1564d
date: 2024-02-05 19:02:53
copyright:
comments:

数组

数组是可以存储一个固定大小的相同类型元素的顺序集合,声明数组的格式为:

type arrayName [ arraySize ];

这是一维数组

arraySize必须是一个大于零的整数常量,type可以是任意有效的C++数据类型,举例:

double balance[10];

类型为double的包含10个元素的数组balance,现在balance是一个可用的数组,可以容纳10个类型为 double 的数字

数组初始化

例如:

balance[4] = 50.0;
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

如上述例子所示,可以逐个对数组里的元素赋值,也可以直接创建并赋值,这里需要注意的是,数组每个元素的索引是从 [0] 开始的,同时给数组赋值时,赋值的元素个数不能超过创建数组的长度,如果创建数组时留空,那么这个数组的长度就是所赋值的元素个数

访问数组元素

例如:

#include <iostream>
#include <iomanip>
 
int main() {
    // n是一个包含10个整数的数组
    int n[ 10 ]; 
    // 初始化数组元素          
    for(int i = 0; i < 10; i++) {
        // 设置元素i为i+100
        n[ i ] = i + 100;
    }
    std::cout << "Element" << std::setw(13) << "Value" << std::endl;
    // 输出数组中每个元素的值                     
    for(int j = 0; j < 10; j++) {
        std::cout << std::setw(7)<< j << std::setw(13) << n[j] << std::endl;
    }
    return 0;
}

setw() 函数只是用来格式化输出

输出结果:

多维数组

常见的就是二维数组:

type arrayName [ x ][ y ];

说白了,二维数组就类似个Excel表格,x和y表示第几行第几列的某个元素,例如:

int a[3][4] = {
    {0, 1, 2, 3},
    {4, 5, 6, 7},
    {8, 9, 10, 11}
};
// 或者:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

访问二维数组:

#include <iostream>
int main() {
    // 一个5行2列的数组
    int a[5][2] = {
        {0,0},
        {1,2},
        {2,4},
        {3,6},
        {4,8}
    };
    // 输出数组中每个元素的值
    for (int i = 0; i < 5; i++)
    for (int j = 0; j < 2; j++) {
        std::cout << "a[" << i << "][" << j << "]: ";
        std::cout << a[i][j]<< std::endl;
    }
    return 0;
}

输出结果:

字符串

在C语言中,字符串实际上是使用null字符 \0 终止的一维字符数组,例如:

char site[7] = {'H', 'e', 'l', 'l', 'o', '!', '\0'};
// 或者:
char site[] = "Hello!";

C++编译器会在初始化字符串数组时,自动把 \0 放在字符串的末尾

示例:

#include <iostream>
int main() {
    char site[7] = {'H', 'e', 'l', 'l', 'o', '!', '\0'};
    std::cout << site << std::endl;
    return 0;
}

下面这些函数可以用来操作以null结尾的数组字符串:

  • strcpy(s1, s2); 复制字符串s2到字符串s1
  • strcat(s1, s2); 连接字符串s2到字符串s1的末尾,连接字符串也可以用 +
  • strlen(s1); 返回字符串s1的长度
  • strcmp(s1, s2); 如果s1和s2是相同的,则返回0;如果s1<s2则返回值小于0;如果s1>s2则返回值大于0
  • strchr(s1, ch); 返回一个指针,指向字符串s1中字符ch的第一次出现的位置
  • strstr(s1, s2); 返回一个指针,指向字符串s1中字符串s2的第一次出现的位置

在C++中,引入了 string 这个类,可以直接定义字符串,例如:

#include <iostream>
// 引入string类
#include <string>
int main() {
    std::string str1 = "Hello";
    std::string str2 = "World";
    std::string str3;
    int len;
    // 复制str1到str3
    str3 = str1;
    std::cout << "str3 : " << str3 << std::endl;
    // 连接str1和str2
    str3 = str1 + str2;
    std::cout << "str1 + str2 : " << str3 << std::endl;
    // 连接后str3的总长度
    len = str3.size();
    std::cout << "str3.size() :  " << len << std::endl;
    return 0;
}

执行结果:

指针

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

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

输出:

指针是一个变量,值为另一个变量的地址,即内存位置的直接地址,指针变量声明:

type *var_name;

type是指针的基类型(数据类型),var_name是指针变量的名称

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

指针的作用:指针变量传递的时候只传递该变量的内存地址,需要调用该变量的时候直接通过内存地址来访问到实际的内容,减少变量内容过大的时候传递耗时

此为个人理解

示例:

#include <iostream>
int main() {
    int var = 20;   // 实际变量的声明
    int *ip;        // 指针变量的声明
    
    ip = &var;       // 在指针变量中存储var的地址

    // 输出实际变量的值
    std::cout << "Value of var variable: ";
    std::cout << var << std::endl;
    
    // 输出在指针变量中存储的地址
    std::cout << "Address stored in ip variable: ";
    std::cout << ip << std::endl;
    
    // 访问指针中地址的值
    std::cout << "Value of *ip variable: ";
    std::cout << *ip << std::endl;
    
    return 0;
}

执行结果:

数组指针

在数组的定义中,一个数组的名称就是指向该数组中第一个元素的一个常量指针,例如:

// 声明一个数组
double myArray[5] = {1, 2, 3, 4, 5};
// 创建一个指针p
double *p;
// 把数组中第一个元素1的内存地址赋值给p
p = myArray;

那么此时,直接使用 *p 就可以访问到 myArray[0] 的值,也就是1

同样的道理,使用 *(myArray +2) 就可以访问到 myArray[2] 的值,也就是3,示例:

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

p是一个指向double型的指针,也就是说它可以存储一个double类型的变量

执行结果:

传递数组给函数

C++中可以通过指定不带索引的数组名来传递一个指向数组的指针

当传递数组给一个函数,数组类型自动转换为指针类型,因而传的实际是数组的内存地址

当然,指针只是其中一种方式,还可以如下所示:

void myFunction(int *param) {
    // param[0]的值就是1
    // param[1]的值就是2
}
int myArray[2] = {1, 2};
myFunction(myArray);

void myFunction(int param[10]) {

}
void myFunction(int param[]) {
    
}

示例:

#include <iostream>

double getAverage(int arr[], int size);

int main() {
    // 带有 5 个元素的整型数组
    int balance[5] = {1000, 2, 3, 17, 50};
    double avg;
    // 传递一个指向数组的指针作为参数
    avg = getAverage(balance, 5);
    // 输出返回值
    std::cout << "平均值是:" << avg << std::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;	// 将sum从int转换为double
    return avg;
}

这里的 ++i 表示先自增,后引用自增后的值

执行结果:

从函数返回数组

C++不允许返回一个完整的数组作为函数的参数,但是可以通过指定不带索引的数组名来返回一个指向数组的指针

如果要从函数返回一个一维数组,则必须声明一个返回指针的函数:

int *myFunction() {
    
}

C++中不能简单地返回指向函数内部的局部数组的指针,因为当函数结束时,局部数组将被销毁,指向它的指针将变得无效,除非定义局部变量为 static 变量,例如:

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

示例:

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

执行结果:

动态分配数组

使用动态分配数组需要在函数内部使用 new 来分配一个数组,并在函数结束时使用 delete 释放该数组,例如:

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

int main() {
    int *result = myFunction();
    
    // 这里是使用result的语句块
    
    delete[] result;	// 使用完毕之后释放该数组
    return 0;
}

示例:

#include <iostream>
// 声明一个创建数组并返回数组的函数,数组参数来自参数size
int *createArray(int size) {
    // 新建一个动态数组指针arr
    int *arr = new int[size];
    // 为数组arr中的元素赋值
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }
    // 返回创建好且赋值好的数组
    return arr;
}

int main() {
    // 调用创建数组的函数,将函数返回的数组赋值给myArray
    int *myArray = createArray(5);
    // 访问myArray数组中的元素
    for (int i = 0; i < 5; i++) {
        std::cout << myArray[i] << " ";
    }
    std::cout << std::endl;
    // 释放内存
    delete[] myArray;
    return 0;
}

执行结果:

空指针

在指针变量声明的时候,如果没有确切的地址可以赋值,为其赋一个 NULL 值是一个良好的编程习惯,这种指针就称为为空指针,例如:

#include <iostream>
int main() {
    int *ptr = NULL;
    std::cout << "ptr 的值是 " << ptr;
    return 0;
}

输出:

在大多数的操作系统上,程序不允许访问地址为0的内存,因为该内存是操作系统保留的

内存地址0有特别重要的意义,它表明该指针不指向一个可访问的内存位置

但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西

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

#include <iostream>
int main() {
    int *ptr = NULL;
    if (ptr) {
        std::cout << "ptr 的值是 " << ptr;
    }
    if (!ptr) {
        std::cout << "ptr的值为空!" << ptr;
    }
    return 0;
}

执行结果:

指针递增递减

如果 ptr 指向一个内存地址为1000的字符,执行 ptr++ 指针 ptr 的值会增加,指向下一个字符元素的地址,由于 ptr 是一个字符指针,每个字符占据1个字节,因此 ptr++ 会将 ptr 的值增加1,执行后 ptr 指向内存地址1001

那么如果当一个指针p加上一个整数n时,结果是指针p向前移动n个元素的大小。例如,如果p是一个int类型的指针,每个int占4个字节,那么 p+1 将指向p所指向的下一个int元素,内存地址也会增加移动了4

定义有点抽象,举个例子:

#include <iostream>
// 定义一个常量
const int MAX = 3;

int main() {
    int var[MAX] = {10, 100, 200};
    int *ptr;
    // 指针中的数组地址
    ptr = var;
    for (int i = 0; i < MAX; i++) {
        std::cout << "Address of var[" << i << "] = ";
        std::cout << ptr << std::endl;
 
        std::cout << "Value of var[" << i << "] = ";
        std::cout << *ptr << std::endl;
        // 移动到下一个元素的位置
        ptr++;
    }
    return 0;
}

执行结果:

递减也是同样的道理:

#include <iostream>
const int MAX = 3;
int main() {
    int var[MAX] = {10, 100, 200};
    int *ptr;
    // 指针中最后一个元素的地址
    ptr = &var[MAX-1];
    for (int i = MAX; i > 0; i--) {
        std::cout << "Address of var[" << i << "] = ";
        std::cout << ptr << std::endl;

        std::cout << "Value of var[" << i << "] = ";
        std::cout << *ptr << std::endl;
        // 从最后一个元素移动到前一个位置
        ptr--;
    }
    return 0;
}

执行结果:

指针的比较

修改上述递增的例子,将判断条件改为变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1]

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

执行结果:

引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字,一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量

引用和指针的区别:

  • 不存在空引用,引用必须连接到一块合法的内存
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象,而指针可以在任何时候指向到另一个对象
  • 引用必须在创建时被初始化,指针可以在任何时间被初始化

引用的声明:

int i = 17;
int &r = i;		// 这样就创建了一个引用

示例:

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

执行结果:

把引用作为参数

示例:

#include <iostream>
// 函数声明
void swap(int &x, int &y);

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

int& x == int &x ,指针同理

执行结果:

把引用作为返回值

通过使用引用来替代指针,会使代码更容易阅读和维护

C++函数可以返回一个引用,方式与返回一个指针类似

当函数返回一个引用时,则返回一个指向返回值的隐式指针,这样,函数就可以放在赋值语句的左边

#include <iostream>

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() {
    std::cout << "改变前的值" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "vals[" << i << "] = ";
        std::cout << vals[i] << std::endl;
    }
    setValues(1) = 20.23; // 改变第2个元素
    setValues(3) = 70.8;  // 改变第4个元素
    std::cout << "改变后的值" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "vals[" << i << "] = ";
        std::cout << vals[i] << std::endl;
    }
    return 0;
}

执行结果:

当返回一个引用时,要注意被引用的对象不能超出作用域

所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用:

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