C++复合数据类型

一.数组的简单排序算法

数组:在程序中为了处理方便,常常需要把具有相同类型的数据类型对象按有序的形式排列起来,形成一组数据,这也就是数组

数组的特点:数组中的数据,在内存中是连续存放的,每个元素占据相同大小的空间,就像排好队一样

1.选择排序

原理:首先在未排序序列中找到最小或最大元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小或最大的元素,然后追加到已排序序列的末尾,以此类推,直到所有元素都排序完毕

提示:选择排序可以使用双重循环轻易的实现

#include"iostream"
using namespace std;

int main() {
    // 选择排序
    int arr[] = {5, 9, 2, 7, 4, 3, 12, 6, 1, 5, 7};
    int size = sizeof(arr) / sizeof(arr[0]);

    for (int i = 0; i < size; i++) {
        for (int j = i + 1; j < size; j++) {
            if (arr[j] < arr[i]) {
                int temp = arr[j];
                arr[j] = arr[i];
                arr[i] = temp;
            }
        }
    }

    for (int num : arr) {
        cout << num << endl;
    }
    return 0;
}

2.冒泡排序

原理:重复地扫描要排序的数列,一次比较两个元素,如果他们的大小顺序错误就将他们交换过来,这样在一次扫描结束之后我们就可以确保最大或最小的值被移动到序列末尾

提示:双重循环同样可以很简单地实现冒泡排序

#include"iostream"
using namespace std;

int main() {
    // 冒泡排序
    int arr[] = {5, 9, 2, 7, 4, 3, 12, 6, 1, 5, 7};
    int size = sizeof(arr) / sizeof(arr[0]);

    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }

    for (int num : arr) {
        cout << num << endl;
    }
    return 0;
}

二.vector模板类

在我们使用数组时有着很大的限制,比如说数组的长度在一开始就已经定义死了(即使是在使用列表初始化的方法省略掉数组中所填写的长度时,它的长度也已经固定死了)

所以在我们使用数组时,数组既不能灵活地调整自己的大小,我们还需要自己获取数组的长度总体而言,使用起来并不方便

所以在这里我们要介绍的就是一个对数组功能进行扩展的一个标准库类型,也就是容器“vector”,顾名思义,vector中容纳着一堆数据对象,其实就是一组类型相同的数据对象的集合

vector的一大特点是它的长度并不是固定的,而是可以进行动态调整的,在我们编写程序时能够提供极大的便利

1.要使用vector需要引入的头文件和命名空间

vector是标准库的一部分,所以如果我们想要使用vector,必须在程序中包含vector头文件,并且使用std命名空间

#include<vector>
using namespace std;

在vector头文件中,对vector这种类型做了定义,在使用#include引入并且指定命名空间std之后,我们就可以直接在代码中使用vector了

2.vector的基本用法

vector<数据类型> 变量名;

vector其实是C++中的一个“类模板”,是用来创建类的“模子”,所以在使用时还必须提供具体的类型信息,也就是说,这个容器中到底要容纳什么类型的数据对象,具体的形式是在vector后面跟一个尖括号,里面填入具体的数据类型

#include <vector>
#include"iostream"
using namespace std;

int main(){
    // 定义一个叫做v1的vector容器,其中存储的数据类型为int
    // 默认初始化方式
    vector<int> v1;

    // 列表初始化
    vector<char> v2 = {'a', 'b', 'c'};
    // 等号可以省略
    vector<char> v3{'d', 'e', 'f'};

    // 直接初始化
    // 定义一个short类型的vector容器,其中有5个默认为0的元素
    vector<short> v4(5);
    // 定义一个long类型的vector容器,其中有5个为100的元素
    vector<long> v5(5, 100);

    // 访问元素
    cout << v5[3] << endl;
    v5[3] = 25;
    cout << v5[3] << endl;
    // 如果想要访问超出当前所设置的vector大小的位置就会直接报错(如访问v4[6])

    cout << endl;

    // 遍历所有元素
    // 在vector容器中包含着一个size函数,直接调用这个函数就可以获取vector容器的长度
    for (int i = 0; i < v5.size(); i++) {
        cout << v5[i] << endl;
    }

    cout << endl;

    // 添加元素,使用push_back函数,会在vector容器的最后面添加上一个元素
    v5.push_back(75);

    // 或者像这样遍历
    for (int num : v5) {
        cout << num << endl;
    }

    cout << endl;

    // 倒序向容器中添加元素
    for (int i = 10; i > 0; i--) {
        v1.push_back(i);
    }
    for (int num : v1) {
        cout << num << endl;
    }

    cout << endl;
};

3.vector和数组的区别

①:数组是更加底层的数据类型,长度固定,功能较少,并且安全性没有保证,但是性能更好,运行更高效

②:vector是模板类,是数组的上层抽象,长度不定,功能强大,但缺点是运行效率较低

此外,除了vector之外,C++11还新增了一个array模板类,它跟数组更加类似,长度是固定的,但更加方便、更加安全,所以在实际应用中,一般推荐对于固定长度的数组使用array,不固定长度的数组使用vector

三.字符串

对于字符串,我们并不陌生,一串字符连在一起就是一个“字符串”,比如用双引号引起来的“Hello World”就是一个字符串字面量

字符串其实就是所谓的“纯文本”,也就是各种文字、数字、符号在一起表达的一串信息,所以字符串就是C++中用来表达和处理文本信息的数据类型

1.标准库类型string

在C++的标准库中,提供了一种用来表示字符串的数据类型string,这种类型能够表示长度可变的字符序列,和vector类似,string类型也定义在命名空间std中,要想使用它必须包含string的头文件

#include<string>
using namespace std;

(1).定义和初始化string

string作为一个标准库类型,它的初始化与vector非常相似

#include <string>
#include"iostream"
using namespace std;

int main(){
    // 初始化
    // 默认初始化
    string s1;

    // 拷贝初始化
    string s2 = s1;
    string s3 = "Hello World";

    cout << s3 << endl;
    cout << endl;

    // 直接初始化
    string s4("hello world");
    string s5(8, 'h');

    cout << s4 << endl;
    cout << s5 << endl;
    cout << endl;

    // 访问字符
    cout << "s5[2] = " << s5[2] << endl;
    // 修改
    s5[2] = 'H';
    cout << s5 <<endl;

    cout << endl;

    // 访问最后一个字符
    cout << s5[s5.size() - 1] << endl;

    cout << endl;

    // 遍历字符串
    for (int i = 0; i < s4.size(); i++) {
        // 将小写转换为大写
        s4[i] = toupper(s4[i]);
    }
    cout << s4 << endl;

    cout << endl;

    // 字符串拼接
    string str1 = "hello", str2("world");
    string str3 = str1 + str2;
    cout << str3 << endl;

    string str4 = str1 + ", " + str2 + "!";
    cout << str4 << endl;
    // 需要注意的是两个字符串之间的拼接,如"str1" + "str2"是错误的,也就是两个字符串字面值常量不能相加

    cout << endl;
};

字符串内字符的访问跟vector内元素的访问类似,需要注意的是:

①:string内字符的索引也是从0开始

②:string同样有一个成员函数size,可以获取字符串的长度

③:索引最大值为(字符串长度 - 1),不能越界访问,如果直接越界访问并且赋值,可能会导致非常严重的后果,出现安全问题

④:如果希望遍历字符串的元素,也可以使用普通for循环和范围for循环,以此获取每个字符

(2).字符串相加

string本身的长度是不固定的,可以通过“相加”的方式扩展一个字符串

其中需要注意的是:

①:字符串相加使用加号来表示,这是算数运算符“+”的运算符重载,含义是“字符串拼接”

②:两个string对象,可以直接进行字符串相加,结果是将两个字符串拼接在一起,得到一个新的string对象返回

③:一个string对象和一个字符串字面值常量可以进行字符串相加,同样是得到一个拼接之后的string对象返回

④:两个字符串字面值常量不能相加

⑤:多个string对象和多个字符串字面值常量可以连续相加,前提是按照左结合律,每次相加必须保证至少有一个string对象

(3).比较字符串

string类还提供几种用来做字符串比较的运算符,“==”和“!=”用来判断两个字符串是否完全一样,而如同“<”,“>”,“<=”,“>=”则是用来比较两个字符串的大小,这些都是关系型运算符的重载

字符串比较的规则为:

①:如果两个字符串的长度相同,每个位置包含的字符也都相同,那么两者“相等”,否则“不相等”

②:如果两个字符串长度不同,而较短的字符串每个字符都跟较长字符串对应的位置上的字符相同,那么较短字符串“小于”较长字符串

如果两个字符串在某一位置上开始不同,那么就比较这两个字符的ASCII码,比较结果就代表两个字符串的大小关系

#include <string>
#include"iostream"
using namespace std;

int main(){
    string str1 = "hello";
    string str2 = "hello world";
    string str3 = "hahaha";
    
    str1 = str2; // false
    str1 < str2; // true
    
    str1 >= str3; // true
};

四.字符数组(C语言风格的字符串)

通过对string的介绍可以发现,字符串就是一串字符的集合,本质上其实就是一个“字符的数组”

在c语言中,确实是用char[]类型来表示字符串的,不过为了区分纯粹的“字符数组”和“字符串”,c语言规定——字符串必须以空字符结束,空字符的ASCII码为0,专门用来标记字符串的结尾,在程序中写作'\0'(也就是结束标志)

#include"iostream"
using namespace std;

// c语言风格的字符串
int main(){
    // str1没有结尾空字符,所以不是一个字符串
    char str1[5] = {'h', 'e', 'l', 'l', 'o'};

    // str2是一个字符串
    char str2[6] = {'h', 'e', 'l', 'l', 'o', '\0'};

    cout << "str1 = " << str1 << endl;
    cout << "str2 = " << str2 << endl;

    cout << endl;

    // 因为在最后还有一个结束标志,所以其实str3的长度不是5,而是6
    char str3[] = "hello";
    cout << "str3 = " << str3 << endl;
    cout << sizeof(str3) << endl;
};

五.读取键盘输入

在程序中往往需要一些交互操作,如果想获取从键盘输入的字符串,可以使用多种方法

1.使用输入操作符读取单词

标准库中提供了iostream,我们可以使用内置的cin对象调用重载的输入操作符>>来读取键盘输入

#include"iostream"
using namespace std;

int main(){
    string str;
    // 读取键盘输入,遇到空白符就停止(空格、回车、制表等)
    cin >> str;
    cout << str << endl;
};

这种方式的特点是:忽略开始的空白符,遇到下一个空白符(空格、回车、制表符等)就会停止,所以如果我们输入“hello world”,那么读取给str的只有“hello”,这相当于读取了一个“单词”

剩下的“world”也没有被丢弃,而是被保存在了输入流的“输入队列”当中,如果我们想要读取更多的输入信息就需要使用更多的string对象来获取

#include"iostream"
using namespace std;

int main(){
    string str1, str2;
    // 使用两个字符串就能够完美的接收到包含一个空白符的字符串了
    cin >> str1 >> str2;
    cout << str1 << str2 << endl;
};

2.使用getline读取一行

如果希望直接读取一整行的输入信息,可以使用getline函数来替代输入操作符

getline(输入流对象, 参数传递的对象);

getline函数有两个参数,一个是输入流对象cin,另一个是保存字符串的string对象,它会一直读取输入流中的内容,直到遇到换行符为止,然后把所有内容保存到string对象中

#include"iostream"
using namespace std;

int main(){
    string str;
    getline(cin, str);
    cout << str << endl;
};

3.使用get读取字符

还有一种方法是调用cin的get函数读取一个字符

#include"iostream"
using namespace std;

int main(){
    char c1, c2;
    c1 = cin.get(); // 将捕获到的字符赋值给c1
    cout << c1 << endl;

    // 这里所捕获到的是回车换行符,如果少了这么一个get函数就会导致程序直接结束,因为c2中传入到的是回车换行符
    cin.get();

    cin.get(c2); // 直接将c2作为参数传给get
    cout << c2 << endl;
};

PS:回车换行符也是一个字符,也会被get捕获,所以就需要使用两个get函数

有两种方式:

①:调用cin.get()函数,不传参数,得到一个字符赋给char类型变量

②:将char类型变量作为参数传入,将捕获的字符付给它,返回的是istream对象

4.get函数还可以读取一行内容

这种方式跟getline很相似,也可以读取一整行内容,以回车结束,主要区别在于,他需要把信息保存在一个char[]类型的字符数组中,调用的是cin的成员函数

#include"iostream"
using namespace std;

int main(){
    // get读取一整行
    char str[20];
    cin.get(str, 20);
    cout << str << endl;

    // get读取一个字符
    cin.get(); // 先读取之前留下的回车符

    cin.get(); // 这一次就是我们真正能获取、传入的有效输入了
};

get函数同样需要传入两个参数,一个是保存信息的字符数组,另一个是字符数组的长度

这跟getline的另一个区别是:键盘输入总是以回车作为结束的

六.简单读写文件

实际应用中,我们往往会遇到读写文件的需求,这也是一种IO操作,整体的用法跟命令行的输入输入非常类似

C++的IO库中提供了专门用于文件输入的ifstream类和用于文件输出的ofstream类,要用它们需要引入头文件fstream。ifstream用于读取文件内容,跟istream的用法类似,也可以通过输入操作符>>来读“单词”(空格分隔),通过getline函数来读取一行,通过get函数来读取一个字符

#include"iostream"
#include"fstream"
using namespace std;

int main(){
    // 输入流
    ifstream input("D:\\ClionProject\\testproject\\test.txt");
    // 输出流
    ofstream output("D:\\ClionProject\\testproject\\output.txt");

    /*
    // 按照单词逐个读取
    string word;
    while (input >> word) {
        cout << word << endl;
    }
    */

    /*
    // 逐行读取
    string line;
    while(getline(input, line)) {
        cout << line << endl;
    }
    */

    /*
    // 逐个字符读取
    char c;
    while (input.get(c)) {
        cout << c << endl;
    }
    */

    // 将输入流中的内容逐个字符输出到输入流当中(并不需要创建文件,因为检测到没有该文件的话会自动给你创建的)
    char c;
    while (input.get(c)) {
        output << c << endl;
    }

    cin.get();
};

七.枚举

在实际应用中,我们经常会遇到某个数据对象只能读取有限个常量值的情况,比如一周有七天,一副扑克牌有四种花色等等,对于这种情况,C++提供了另一种批量创建符号常量的方式,可以替代const,这也就是枚举类型enum

1.枚举类型的定义

枚举类型的定义和结构体非常像,需要使用enum关键字

// 定义枚举类型
enum 枚举类型名 {
    枚举量1, 枚举量2, ..., 枚举量n
};

与结构体不同的是,枚举类型内只有有限个名字,他们都各自代表一个常量,被称为“枚举量”

并且我们需要注意的是:

①:默认情况下,会将整数值赋给枚举量

②:枚举量默认从0开始,每个枚举量依次+1,所以在我们定义枚举量时,如我们一共定义了n个枚举量,那么他们的枚举量分别所对应的常量值就是0~n-1

③:可以通过对枚举量赋值,显式地设置每个枚举量的值

2.使用枚举类型

如果我们想要使用枚举类型,那么只需要在创建枚举类型的对象之后将对应类型的枚举量赋值,如果打印它的值,将会得到对应的整数

#include"iostream"
using namespace std;

// 定义一个枚举类型
enum Week {
    // 我们可以显式书写一个枚举量的值
    Mon, Tue, Wed, Thu, Fri = 10, Sat, Sun
};

int main(){
    Week w1 = Tue;
    // 这里输出的值是枚举量,也就是说如果是Mon则是0,Sun则是6
    cout << w1 << endl;

    // 直接定义Week w = 某个特定的数值(如5),这样的书写方式是错误的,正确的书写方式应是:
    // 其实本质就是做了一个强制类型转换,把一个整数类型强制转换成了一个枚举类型
    Week w2 = Week(5);
    cout << w2 << endl;

    // 因为我们在上方自己定义了Fri的枚举量,所以这里显示的就是我们定义的值
    Week w3 = Fri;
    cout << w3 << endl;
    
    // 因为枚举类型中枚举量的计算是前一个枚举量+1,所以Sat值为11,以此类推
    Week w4 = Sat;
    cout << w4 << endl;
};

这里需要注意的是:

①:如果直接用一个整型值对枚举类型赋值,将会报错,因为类型不匹配

②:可以通过强制类型转换将一个整型值赋值给枚举对象

③:最初的枚举类型只有列出的值是有效的,而现在C++通过强制类型转换允许扩大枚举类型合法值的范围,不过一般使用枚举类型都要避免直接强转赋值

八.指针

在计算机中存储的数据都存放在内存中,访问内存的最小单元是“字节”(byte),所有的数据就保存在内存中具有连续编号的一串字节里

而指针,顾名思义,是“指向”另外一种数据类型的复合类型,指针是C/C++中一种特殊的数据类型,它所保存的信息其实是另外一个数据对象在内存中的“地址”,通过指针可以访问到指向的那个数据对象,所以这是一种间接访问对象的方法

1.指针的定义

指针的定义语法形式为:

类型* 指针变量名;

这里的类型指的是指针所指向的数据类型,后面加上星号“*”,然后跟指针变量的名称,指针在定义的时候可以不做初始化,相比一般的变量声明,看起来指针只是多了一个星号“*”而已

例如:

#include"iostream"
using namespace std;

int main(){
    int* p1; // 指向int类型数据的指针
    long* p2; // 指向long类型数据的指针
    // 因为都是指针,所以二者的大小跟指向的数据类型无关,全都为8
    cout << sizeof(p1) << endl;
    cout << sizeof(p2) << endl;
};

这里的两个p都是指针,分别指向int和long类型的数据对象

指针的本质就是一个整数表示的内存地址,它本身在内存中所占的大小和系统环境有关,而跟指向的数据类型无关(在64位编译环境中占8个字节,32位编译环境中则是4个字节)

2.获取对象地址给指针赋值

指针保存的是数据对象的内存地址,所以可以用地址给出指针赋值,获取对象地址的方式是使用“取地址操作符(&)”

#include"iostream"
using namespace std;

int main(){
    int a = 50;
    int b = 100;
    cout << "a = " << a << endl;
    cout << "a's address = " << &a << endl;
    cout << "b's address = " << &b << endl;

    int* p = &b; // 定义一个指向b的指针
    p = &a; // 将指针的指向换为a,这是可行的

    // 因为换了p的指向,所以这里p指向a
    cout << "p = " << p << endl;
};

把指针看作一个变量,它当然可以先指向一个对象,再指向另一个不同的对象

3.通过指针访问对象

指针指向数据对象之后,可以通过指针来访问对象,访问方式是使用“解引用操作符(*)”

#include"iostream"
using namespace std;

int main(){
    int num = 10;
    int* p = &num;
    cout << "p = " << *p << endl;

    // 修改p指向的地址中存储的值
    *p = 100;
    cout << "num = " << num << endl;
};

在这里由于p指向了num,所以*p可以近似看作num

4.各种指针类型

①:无效指针

定义一个指针之后,如果不进行初始化,那么它的内容是不固定的,如果这时把它的内容当成一个地址去访问,就可能访问的是不存在的对象,更有甚者,如果访问到的是系统核心内存区域,修改其中的内容会导致系统崩溃,这样的指针就是“无效指针”,也被叫做“野指针”

int* p; // 定义出了一个没有初始化的“野指针”

一定要在使用指针的时候注意对其进行初始化

②:空指针

如果先定义了一个指针,但目前还不知道要将它指向哪个对象,那么这时可以把它初始化为“空指针”,空指针并不指向任何对象

#include"iostream"
using namespace std;

int main(){
    // 几种定义空指针的方式
    int* p = nullptr; // 空指针字面值
    p = NULL; // 预处理变量
    p = 0; // 0值

    int num = 100;
    // p = num; // 这样的书写方式是错误的,int变量不能直接赋值给指针

    cout << "p = " << p << endl; // 输出零地址
    // cout << "*p = " << *p << endl; // 这样的书写方式是错误的,不能访问0地址的内容
};

空指针有几种定义方式:

Ⅰ:使用字面值nullptr,这是C++11引入的方式

Ⅱ:使用预处理变量NULL,这是老版本的方式

Ⅲ:直接使用0值

Ⅳ:另外需要注意的是:不能直接用整型变量给指针赋值,即使值为0也不行,所以其实我们可以看出空指针中保存的就是0值,一般被我们叫做“0地址”,0地址在内存中真实存在,所以也不允许访问

空指针的用处为——在程序中进行判断,看一个指针是否指向了数据对象

③:void指针

一般来说指针的类型必须和指向的对象类型匹配,否则就会报错,不过void指针有些特殊,可以用来存放任意对象的地址(其实感觉和vector有点像是不是)

#include"iostream"
using namespace std;

int main(){
    int i = 100;
    string s = "hello world";

    void* p = &i;
    cout << "p = " << p << endl;
    
    p = &s;
    cout << "p = " << p << endl;
    // 注意:并不能通过解引用符访问对象
};

void指针表示只知道“保存了一个地址”,至于这个地址对应的数据对象是什么类型并不清楚,所以不能通过void指针访问对象

④:指向指针的指针

指针本身也是一个数据对象,也有自己的内存地址,所以可以让一个指针保存另一个指针的地址,这也就是“指向指针的指针”,有时也叫做“二级指针”,形式上可以用两个连续的星号来表示,类似的,如果是三级指针(指向二级指针的指针)就是三个连续的星号表示

#include"iostream"
using namespace std;

int main(){
    int i = 100;
    int* p = &i;
    int** pp = &p;

    cout << "p = " << p << endl;
    cout << "*p = " << *p << endl;
    cout << "pp = " << pp << endl;
    cout << "**pp = " << *pp << endl;
};

5.指针和const的关系

指针可以和const修饰符结合,这可以有两种形式,一种是指针指向的是一个常量,另一种是指针本身是一个常量

①:指向常量的指针

指针指向的是一个常量,所以只能访问数据,不能通过指针对数据进行修改,不过指针本身是变量,可以指向另外的数据对象,这时应该把const加在类型之前

#include"iostream"
using namespace std;

int main(){
    const int num1 = 10, num2 = 100;
    // *p = &num1; // 这样的定义方式是错误的,类型并不匹配
    const int* p = &num1; // 这样的定义方式才是正确的
    
    p = &num2; // p可以指向相同类型的另一个变量
    
    int num3 = 1000;
    p = &num3; // p可以指向变量
    
    // *p = 100; // 这样的书写方式是错误的,并不能通过常量p指针更改变量中的值
    
};

②:指针常量(const指针)

指针本身是一个数据对象,所以也可以区分变量和常量,如果指针本身是一个常量,那么就意味着它保存的地址不能更改,也就是它永远指向同一个对象,而数据对象的内容是可以通过指针改变的,这种指针一般叫做“指针常量”

指针常量在定义的时候需要在星号后,标识符之前加上const

#include"iostream"
using namespace std;

int main(){
    int num = 100;
    int* const p = &num;
    *p = 1000;
    cout << "num = " << num << endl;
    
    int test = 10;
    // p = &test; // 这样的书写方式是错误的,不能更改指针常量的指向
    
    const int Const = 20;
    const int* const pp = &Const; // 一个指向常量的常量指针 
};

6.指针和数组

①:数组名

用到数组名时,编译器一般都会把它转换成指针,这个指针就指向数组的第一个元素,所以我们也可以用数组名来给指针赋值

#include"iostream"
using namespace std;

int main(){
    int arr[] = {1, 2, 3, 4, 5};
    cout << "arr = " << arr << endl;
    cout << "&arr[0] = " << &arr[0] << endl;
    cout << "&arr[1] = " << &arr[1] << endl;

    int* p =arr;
    cout << "*p = " << *p << endl;
    *p = 100; // 这里指向的是arr[0]

    for (int num : arr) {
        cout << num << endl;
    }
};

正因为数组名被认为是指针,所以不能直接使用数组名对另一个数组赋值,数组也不允许这样的直接拷贝

int arr1[] = {1, 2, 3, 4, 5};
// int arr2[5] = arr; // 这样的书写形式是错误的,数组不能这样直接拷贝

②:指针运算

如果对指针进行加减操作,就是对一个指针加/减一个整数值,得到的结果仍然是指针,新指针指向的数据元素跟原指针指向的相比移动了对应个数据单位

    int num[] = {1, 2, 3, 4, 5};
    int* p = &num; // 此时p指向num[0]
    p += 1; // 此时p的地址值+4,因为int型数据所占的字节数为4,并且此时p指向num[1]
    

③:指针和数组下标

*arr; // 等于arr[0]
*(arr + 1); // 等于arr[1]

数组名arr其实就是指针,这也就是说,我们通过指针来访问数组元素的效果是和使用下标来访问数组元素一样的,进而发现,遍历元素时所使用的“范围for循环”其实就是让指针不停的向后移动并依次访问其中的元素

④:指针数组和数组指针

指针和数组这两种类型其实可以结合在一起,也就是接下来介绍的“指针数组”和“数组指针”

Ⅰ:指针数组:一个数组,他的所有元素都是相同类型的指针

Ⅱ:数组指针:一个指针,一个指向数组的指针

#include"iostream"
using namespace std;

int main(){
    int arr[] = {1, 2, 3, 4, 5};
    int* p1[5]; // 指针数组,其中的每一个元素都是类型相同的指针
    int(* p2)[5]; // 数组指针,指向一个int数组,其中包含5个元素
};

九.引用

1.概念:

我们可以在C++中为数据对象另外起一个名字,这叫做“引用(reference)”

2.引用的用法

在做声明时,我们可以在变量名之前加上“&”符号,表示它是另一个变量的引用,引用必须被初始化

#include"iostream"
using namespace std;

int main(){
    int num = 100;
    int& ref = num; // 设置一个num的引用,名字为ref
    // 注意引用必须初始化
    
    cout << "ref = " << ref << endl;

    // 你会发现二者的地址是一样的
    cout << "a's address = " << &num << endl;
    cout << "ref's address = " << &ref << endl;
};

引用的本质就是一个“别名”,它本身并不是数据对象,所以不会存储数据,而是和初始值绑定在一起,绑定之后就不能再绑定别的对象了。

定义了应用之后,对引用做的所有操作就像直接操作绑定的原始变量一样,所以引用也是一种间接访问数据对象的方式

3.对常量的引用

可以把引用绑定到一个常量上,这就是“对常量的引用”,很显然,对常量的引用是常量的别名,绑定的对象不能修改,所以也不能做赋值操作

#include"iostream"
using namespace std;

int main(){
    const int num = 100;
    
    // 这时使用int& ref = num;会报错,因为此时引用的类型和常量不匹配
    const int& ref = num;
    // 注意不能对常量赋值
};

对常量的引用有时候也会直接简称“常量引用”,因为引用只是别名,本身不是数据对象,所以这只能代表“对一个常量的引用”,而不会像是“常量指针”一般引起混淆

常量引用和普通变量的引用不同,它的初始化要求宽松很多,只要是可以转换成它指定类型的所有表达式都可以用来做初始化

#include"iostream"
using namespace std;

int main(){
    // 使用字面值常量进行初始化
    const int& ref1 = 100;
    cout << ref1 << endl;

    // 使用变量进行初始化
    int num = 1000;
    const int& ref2 = num;
    cout << ref2 << endl;

    // 转变绑定的变量类型的初始化
    double db = 7.135154135;
    const int& ref3 = db;
    cout << ref3 << endl;
};

注意:使用了const定义的引用不能再对其绑定的变量(或常量)进行更改

4.引用和指针

其实引用和指针有着很多类似的地方

#include"iostream"
using namespace std;

int main(){
    int num = 100;

    // 引用/指针常量
    int& ref = num;
    int* const p = &num;

    // 二者都可以修改指向/绑定的对象的数值
    ref = 50;
    cout << "ref = " << num << endl;
    *p = 75;
    cout << "p = " << num << endl;

    cout << "num's address = " << &num << endl;
    cout << "ref's address = " << &ref << endl;
    // 指针的地址是内存给它重新分配的,只不过其中保存的值为指向的对象的地址
    cout << "p's value = " << p << endl;
    cout << "p's address = " << &p << endl;
};

引用类似于指针常量,但是不等同于指针常量

指针常量本身还是一个数据对象,它保存着另一个对象的地址,而且不能更改,而引用只是“别名”,它会被编译器直接翻译成所绑定的原始变量,所以我们会看到引用和原始对象的地址是一样的,引用并没有占用额外的内存空间,这也是为什么不会有“指向引用的指针”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值