C++学习全教程(Day2)

一、数组

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

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

1、数组的定义

数组的定义形式如下:

首先需要声明类型r数组中所有元素必须具有相同的数据类型;

数组名是一个标识符;后面跟着中括号,里面定义了数组中元素的个数,也就是数组的“长度”;

元素个数也是类型的一部分,所以必须是确定的;

需要注意的是:

        对数组做初始化,要使用花括号括起来的数值序列;

        如果做了初始化,数组定义时的元素个数可以省略,编译器可以根据初始化列表自动推断出来;

        初始值的个数,不能超过指定的元素个数;

        初始值的个数,如果小于元素个数,那么会用列表中的值初始化靠前的元素;剩余元素用默认值填充,整型的默认值就是0;

        如果没有做初始化,数组中元素的值都是未定义的;这一点和普通的局部变量一致;|

2、数组的访问

(1)访问数组元素

数组元素在内存中是连续存放的,它们排好了队之后就会有一个队伍中的编号,称为“索引”,也叫“下标”;通过下标就可以快速访问每个元素了,具体形式为:

这里也是用了中括号来表示元素下标位置,被称为“下标运算符”。比如 a[2]就表示数组a中下标为2的元素,可以取它的值输出,也可以对它赋值。

#include<iostream>using namespace std;int main()
{int a[] = {1,2,3,4,5,6};cout << "a[1] = " << a[1] << endl;a[1] = 666;cout << "a[1] = " << a[1] << endl;cin.get();
}

运行结果:

需要注意的是:

        数组的下标从o开始;

        因此 a[2]访问的并不是数组 a的第2个元素,而是第三个元素;一个长度为10的数组,下标范围是o~9,而不是1~10;

        合理的下标,不能小于o,也不能大于(数组长度-1);否则就会出现数组下标越界;

(2)数组的大小

所有的变量,都会在内存中占据一定大小的空间;而数据类型就决定了它具体的大小。而对于数组这样的“复合类型”,由于每个元素类型相同,因此占据空间大小的计算遵循下面的简单公式:

#include<iostream>using namespace std;int main()
{int a[] = {1,2,3,4,5,6};cout << "a[1] = " << a[1] << endl;a[1] = 666;cout << "a[1] = " << a[1] << endl;//获取数组大小的长度cout << "数组a所占空间大小为:" << sizeof(a) << endl;cout << "数组a中每个元素的大小:" << sizeof(a[0]) << endl;int size;size = sizeof(a) / sizeof(a[0]);cout << "数组a的长度为:" << size << endl;cin.get();
}

运行结果:

3、多维数组

C++中本质上没有“多维数组”这种东西,所谓的“多维数组”,其实就是“数组的数组”。

        二维数组int arr[3][4]表示: arr是一个有三个元素的数组,其中的每个元素都是一个int 数组,包含4个元素;

        三维数组int arr2[2][5][10]表示: arr2是一个长度为2的数组,其中每个元素都是一个二维数组;这个二维数组有5个元素,每个元素都是一个长度为10的int 数组;

 一般最常见的就是二维数组。它有两个“维度”,第一个维度表示数组本身的长度,第二个表示每个元素的长度,一般分别把它们叫做“行”和“列”。

(1)多维数组的初始化

和普通的“一维”数组一样,多维数组初始化时,也可以用花括号括起来的一组数。使用嵌套的花括号可以让不同的维度更清晰:

需要注意:

        内嵌的花括号不是必需的,因为数组中的元素在内存中连续存放,可以用一个花括号将所有数据括在一起;

        初始值的个数,可以小于数组定义的长度,其它元素初始化为0值;这一点对整个二维数组和每一行的一维数组都适用;

        如果省略嵌套的花括号,当初始值个数小于总元素个数时,会按照顺序依次填充(填满第一行,才填第二行);其它元素初始化为О值;

        多维数组的维度,可以省略第一个,由编译器自动推断;即二维数组可以省略行数,但不能省略列数

#include<iostream>using namespace std;int main()
{//初始化int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int b[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12}};int c[3][4] = { 1,2,3,4 };int d[][4] = { 1,2,3,4,5,6 };//访问cout << "b[1][1] = " << b[1][1] << endl;b[1][1] = 666;cout << "b[1][1] = " << b[1][1] << endl;//遍历//计算二维数组中的行数和列数cout << "二维数组b的总大小:" << sizeof(b) << endl;  //大小说的是字节cout << "二维数组每一行的大小:" << sizeof(b[0]) << endl;cout << "二维数组中每一个元素的大小:" << sizeof(b[0][0]) << endl;int rowCnt = sizeof(b) / sizeof(b[0]);  //行数int colCnt = sizeof(b[0]) / sizeof(b[0][0]); //列数cout << "rowCnt = " << rowCnt << endl;cout << "colCnt = " << colCnt << endl;for (int i = 0; i < rowCnt; i++){for (int j = 0; j < colCnt; j++){cout << b[i][j] << "\t";}cout << endl;}cin.get();
}

运行结果:

4、数组的排序

数组排序指的是给定一个数组,要求把其中的元素按照从小到大(或从大到小)顺序排列

这是一个非常经典的需求,有各种不同的算法可以实现。我们这里介绍两种最基本、最简单的排序算法。

(1)选择排序

选择排序是一种简单直观的排序算法。

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

#include<iostream>using namespace std;int main()
{int a[] = {2,5,8,4,9,1,6,3,7,0};//计算数组a的大小int size = sizeof(a) / sizeof(a[0]);//选择排序for (int i = 0; i < size; i++){for (int j = i + 1; j < size; j++){if (a[j] < a[i]){int temp = a[j];a[j] = a[i];a[i] = temp;}}}for (int num : a){cout << num << "\t";}cout << endl;cin.get();
}

运行结果:

(2)冒泡排序

冒泡排序也是一种简单的排序算法。

它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾。这个算法的名字由来,就是因为越小的元素会经由交换,慢慢“浮”到数列的顶端。

#include<iostream>using namespace std;int main()
{int a[] = {2,5,8,4,9,1,6,3,7,0};//计算数组a的大小int size = sizeof(a) / sizeof(a[0]);//冒泡排序for (int i = 0; i < size; i++){for (int j = 0; j < size - i - 1; j++){if (a[j] > a[j + 1]){int temp = a[j + 1];a[j + 1] = a[j];a[j] = temp;}}}for (int num : a){cout << num << "\t";}cout << endl;cin.get();
}

运行结果:

二、模板类vector

数组尽管很灵活,但使用起来还是很多不方便。为此,C++语言定义了扩展的“抽象数据类型”(Abstract Data Type,ADT),放在“标准库”中。

对数组功能进行扩展的一个标准库类型,就是“容器”vector。顾名思义,vector“容纳”着一堆数据对象,其实就是一组类型相同的数据对象的集合。

1、头文件和命名空间

vector是标准库的一部分。要想使用vector,必须在程序中包含<vector>头文件,并使用std命名空间。

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

2、 vector的基本用法

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

#include<iostream>
#include<vector>using namespace std;int main()
{//默认初始化vector<int> v1; //定义了整型的容器,容器名:v1//列表初始化vector<char> v2 = { 'a','b','c' };vector<char> v3{ 'a','b','c' };//直接初始化vector<short> v4(5);  //定义了大小为5的短整型容器vector<double> v5(5,100);//定义了大小为5的double容器,且每个值都为100//访问元素cout << v5[2] << endl;v5[2] = 666;cout << v5[2] << endl;//遍历所有元素for (int i = 0; i < v5.size(); i++){cout << v5[i] << "\t";}cout << endl;//添加元素v5.push_back(69);for (int num : v5){cout << num << "\t";}cout << endl;//向容器中添加倒序的元素for (int i = 10; i > 0; i--){v1.push_back(i);}//遍历容器v1for (int num : v1){cout << num << "\t";}cout << endl;cin.get();
}

运行结果:

2、vector和数组的区别

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

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

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

三、字符串

字符串我们并不陌生。之前已经介绍过,一串字符连在一起就是一个“字符串”,比如用双引号引起来的“Hello World !”就是一个字符串字面值。

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

1、标准库类型string

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

(1)定义和初始化 string

我们已经接触过C++中几种不同的初始化方式, string 也是一个标准库类型,它的初始化与vector非常相似。

#include<iostream>
#include<string>using namespace std;int main()
{//初始化string s1;//拷贝初始化string s2 = s1;string s3 = "hello world";//直接初始化string s4("hello world");string s5(8, 'h');cout << s5 << endl;//访问字符cout << "s4[1] = " << s4[1] << endl;s4[0] = 'H';cout << s4 << endl;cin.get();
}

运行结果:

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

        string内字符的索引,也是从o开始;

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

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

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

#include<iostream>
#include<string>using namespace std;int main()
{//初始化string s1;//拷贝初始化string s2 = s1;string s3 = "hello world";//直接初始化string s4("hello world");string s5(8, 'h');cout << s5 << endl;//访问字符cout << "s4[1] = " << s4[1] << endl;s4[0] = 'H';cout << s4 << endl;//遍历for (int i = 0; i < s4.size(); i++){s4[i] = toupper(s4[i]);  //toupper:小写转换大写}cout << s4 << endl;cin.get();
}

运行结果:

(2)字符串相加

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

需要注意:

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

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

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

        两个字符串字面值常量,不能相加;

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

(3)比较字符串

字符串比较的规则为:

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

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

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

2、字符数组

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

在C语言中,确实是用char[]类型来表示字符串的;不过为了区分纯粹的“字符数组”和“字符串”,C语言规定:字符串必须以空字符结束。空字符的ASClI码为o,专门用来标记字符串的结尾,在程序中写作\0'。

#include<iostream>using namespace std;int main()
{char str1[5] = { 'g','y','s','z','s' };//str1不是一个字符串char str2[6] = { 'g','y','s','z','s','\0'};//str2是一个字符串cout << "str1 = " << str1 << endl;cout << "str2 = " << str2 << endl;cin.get();
}

运行结果:

3、读取输入的字符串

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

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

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

剩下的内容“world”其实也没有丢,而是保存在了输入流的“输入队列”里。如果我们想读取更多的输入信息,就需要使用更多的string对象来获取:

(2〉使用getline读取一行

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

getline函数有两个参数:一个是输入流对象cin,另一个是保存字符串的string 对象;它会一直读取输入流中的内容,直到遇到换行符为止,然后把所有内容保存到string 对象中。所以现在可以完整读取一整行信息了。

#include<iostream>
#include<string>using namespace std;int main()
{//使用getline读取一行信息string str1;getline(cin, str1);cin.get();cout << "str1 = " << str1 << endl;cin.get();
}

运行结果:

(3)使用get读取字符

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

有两种方式:

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

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

(4)读写文件

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

#include<iostream>
#include<fstream>
#include<string>using namespace std;int main()
{ifstream input("1.txt"); //通过input对象来从文件中读取数据ofstream output("2.txt");//1.按照单词逐个读取/*string word;while (input >> word){cout << word << endl;}*///2.逐行读取string line;while (getline(input, line)){cout << line << endl;//output << line << endl;}cin.get();
}

运行结果:

四、结构体

C/C++中提供了另一种更加灵活的数据结构——结构体。结构体是用户自定义的复合数据结构,里面可以包含多个不同类型的数据对象。

1、结构体的定义和声明

声明一个结构体需要使用struct关键字,具体形式如下:

#include<iostream>using namespace std;//定义1个结构体
struct  student
{string name;int age;double score;
};int main()
{//创建对象并初始化student s1 = { "ljl",18,99 };cin.get();
}

2、访问结构体中数据

访问结构体变量中的数据成员,可以使用成员运算符点号.),后面跟上数据成员的名称。例如stu.name就可以访问stu对象的name成员。

#include<iostream>using namespace std;//定义1个结构体
struct  student
{string name;int age;double score;
};//输出一个数据对象的完整信息
void pintinfo(student s)
{//访问数据cout << "学生姓名:" << s.name << "\t年龄:" << s.age << "\t成绩:" << s.score << endl;
}int main()
{//创建对象并初始化student s1 = { "ljl",18,99 };student s2;s2.age = 20;s2.name = "xiaoming";s2.score = 80;pintinfo(s1);pintinfo(s2);cin.get();
}

运行结果:

3、结构体数组

可以把结构体和数组结合起来,创建结构体的数组。顾名思义,结构体数组就是元素为结构体的数组,它的定义和访问跟普通的数组完全一样。

#include<iostream>using namespace std;//定义1个结构体
struct  student
{string name;int age;double score;
};//输出一个数据对象的完整信息
void pintinfo(student s)
{//访问数据cout << "学生姓名:" << s.name << "\t年龄:" << s.age << "\t成绩:" << s.score << endl;
}int main()
{//创建对象并初始化student s1 = { "ljl",18,99 };student s2;s2.age = 20;s2.name = "xiaoming";s2.score = 80;pintinfo(s1);pintinfo(s2);//结构体数组student s3[3] = {{"小王",18,88},{"小张",19,66},{"小钱",20,99}};pintinfo(s3[0]);cin.get();
}

运行结果:

五、枚举

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

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

需要注意的是:

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

        枚举量默认从0开始,每个枚举量依次加1;所以上面week枚举类型中,一周七天枚举量分别对应着0~6的常量值;

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

#include<iostream>using namespace std;//定义1个枚举类型
enum Week {Mon, Tue, Wed, Thu, Fri, Sta, Sun
};int main()
{Week w1 = Mon, w2 = Tue;cout << "w1 = " << w1 << endl;cout << "w2 = " << w2 << endl;cin.get();
}

运行结果:

六、指针  ---存放是地址

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

1、指针的定义

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

#include<iostream>using namespace std;int main()
{int* p;long* p1;long long* p2;cout << "p在内存中的长度为:" << sizeof(p) << endl;cout << "p1在内存中的长度为:" << sizeof(p1) << endl;cout << "p2在内存中的长度为:" << sizeof(p2) << endl;cin.get();
}

运行结果:

指针在64位系统下占据8个字节,在32位系统下占4个字节

2、指针的用法

(1)获取对象地址给指针赋值

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

#include<iostream>using namespace std;int main()
{int* p;long* p1;long long* p2;cout << "p在内存中的长度为:" << sizeof(p) << endl;cout << "p1在内存中的长度为:" << sizeof(p1) << endl;cout << "p2在内存中的长度为:" << sizeof(p2) << endl;//指针的使用int a = 10;long b = 20;p = &a;p1 = &b;cout << "a的地址为:" << &a << endl;cout << "b的地址为:" << &b << endl;cout << "p:" << p << endl;cout << "p1:" << p1 << endl;cin.get();
}

运行结果:

(2)通过指针访问对象

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

在这里由于p指向了a,所以*p可以等同于a。

#include<iostream>using namespace std;int main()
{int* p;long* p1;long long* p2;cout << "p在内存中的长度为:" << sizeof(p) << endl;cout << "p1在内存中的长度为:" << sizeof(p1) << endl;cout << "p2在内存中的长度为:" << sizeof(p2) << endl;//指针的使用int a = 10;long b = 20;p = &a;p1 = &b;cout << "a的地址为:" << &a << endl;cout << "b的地址为:" << &b << endl;cout << "p:" << p << endl;cout << "p1:" << p1 << endl;*p = 666;cout << "a = " << a << endl;cin.get();
}

运行结果:

3、无效指针、空指针和 void*指针

(1)无效指针

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

指针非常灵活非常强大,但野指针非常危险。所以建议使用指针的时候,一定要先初始化,让它指向真实的对象。

(2)空指针

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

(3)void* 指针

一般来说,指针的类型必须和指向的对象类型匹配,否则就会报错。不过有一种指针比较特殊,可以用来存放任意对象的地址,这种指针的类型是void*。

4、指向指针的指针

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

#include<iostream>using namespace std;int main()
{int* p;long* p1;long long* p2;cout << "p在内存中的长度为:" << sizeof(p) << endl;cout << "p1在内存中的长度为:" << sizeof(p1) << endl;cout << "p2在内存中的长度为:" << sizeof(p2) << endl;//指针的使用int a = 10;long b = 20;p = &a;p1 = &b;cout << "a的地址为:" << &a << endl;cout << "b的地址为:" << &b << endl;cout << "p:" << p << endl;cout << "p1:" << p1 << endl;*p = 666;cout << "a = " << a << endl;cout << "--------------------------------" << endl;//二级指针int i = 100;int* pi = &i;int** ppi = &pi;cout << "i = " << i << endl;cout << "pi = " << pi << endl;cout << "ppi = " << ppi << endl;cout << "*pi = " << *pi << endl;cout << "*ppi = " << *ppi << endl;cout << "**ppi = " << **ppi << endl;cin.get();
}

运行结果:

5、指针和const

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

(1)指向常量的指针

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

这里发现,pc是一个指向常量的指针,但其实把一个变量i的地址赋给它也是可以的;编译器只是不允许通过指针pc去间接更改数据对象。

(2)指针常量(const 指针)

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

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

6、指针和数组

(1)数组名

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

运行结果:

(2)指针运算

如果对指针pia 做加1操作,我们会发现它保存的地址直接加了4,这其实是指向了下一个int类型数据对象:

所谓的“指针运算”,就是直接对一个指针加/减一个整数值,得到的结果仍然是指针。新指针指向的数据元素,跟原指针指向的相比移动了对应个数据单位。

(3)指针数组和数组指针

指针和数组这两种类型可以结合在一起,这就是“指针数组”和“数组指针”。

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

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

运行结果:

七、引用

1、引用的用法

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

#include<iostream>using namespace std;int main()
{int a = 888, b = 666;int& ref = a;cout << "ref = " << ref << endl;cout << "a的地址为: " << &a << endl;cout << "ref的地址为: " << &ref << endl;cin.get();
}

运行结果:

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

2、对常量的引用

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

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

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

3、指针和引用

(1)引用和指针常量

事实上,引用的行为,非常类似于“指针常量”,也就是只能指向唯一的对象、不能更改的指针。

#include<iostream>using namespace std;int main()
{int a = 66;//引用和指针常量int& ref = a;int* const p = &a;ref = 666;cout << "a = " << a << endl;*p = 888;cout << "a = " << a << endl;cout << "a的地址为:" << &a << endl;cout << "ref的地址为:" << &ref << endl;cout << "p的地址为:" << &p << endl;cout << "p的值为: " << p << endl;//绑定指针的引用int* ptr = &a;int*& pref = ptr;  //pref是ptr指针的别名*ptr = 100;cout << "a = " << a << endl;cin.get();
}

运行结果:

八、函数

8.1、函数的定义

一个完整的函数定义主要包括以下部分:

        返回类型:调用函数之后,返回结果的数据类型;

        函数名:用来命名代码块的标识符,在当前作用域内唯一;

        参数列表:参数表示函数调用时需要传入的数据,一般叫做“形参”;放在函数名后的小括号里,可以有o个或多个,用逗号隔开;

        函数体:函数要执行的语句块,用花括号括起来。        

#include<iostream>using namespace std;//定义一个平方函数
int square(int x)
{int y = x * x;return y;
}int main()
{cout << "请输入一个整数:";int num;cin >> num;cin.get();cout << "num的平方为:" << square(num) << endl;cin.get();
}

运行结果:

8.1.2、局部变量的生命周期

对于花括号内定义的变量,具有“块作用域”,在花括号外就不可见了。函数体都是语句块,而主函数main本身也是一个函数;所以在main 中定义的所有变量、所有函数形参和在函数体内部定义的变量,都具有块作用域,统称为“局部变量”。局部变量仅在函数作用域内部可见

在C++中,作用域指的是变量名字的可见范围;变量不可见,并不代表变量所指代的数据对象就销毁了。这是两个不同的概念:

        作用域:针对名字而言,是程序文本中的一部分,名字在这部分可见;

        生命周期:针对数据对象而言,是程序在执行过程中,对象从创建到销毁的时间段

基于作用域,变量可以分为“局部变量”和“全局变量”。对于全局变量而言,名字全局可见,对象也只有在程序结束时才销毁

而对于局部变量代表的数据对象,基于生命周期,又可以分为“自动对象”和“静态对象”。

(1)自动对象

平常代码中定义的普通局部变量,生命周期为:在程序执行到变量定义语句时创建,在程序运行到当前块末尾时销毁。这样的对象称为“自动对象”。

形参也是一种自动对象。形参定义在函数体作用域内,一旦函数终止,形参也就被销毁了。

对于自动对象来说,它的生命周期和作用域是一致的

(2)静态对象

如果希望延长一个局部变量的生命周期,让它在作用域外依然保留,可以在定义局部变量时加上static关键字;这样的对象叫做“局部静态对象”。

局部静态对象只有局部的作用域,在块外依然是不可见的;但是它的生命周期贯穿整个程序运行过程,只有在程序结束时才被销毁,这一点与全局变量类似。

8.1.3、分离式编译和头文件

(1)分离式编译

当程序越来越复杂,我们就会希望代码分散到不同的文件中来做管理。C++支持分离式编译,这就可以把函数单独放在一个文件,独立编译之后链接运行。

比如可以把复制字符串的函数单独保存成一个文件 copy_string.cpp:

(2)编写头文件

对于一个项目而言,有些定义可能是所有文件共用的,比如一些常量、结构体/类,以及功能性的函数。于是每次需要引入时,都得做一堆声明——这显然太麻烦了。

一个好方法是,把它们定义在同一个文件中,需要时用一句#include 统一引入就可以了,就像使用库一样。这样的文件以.h作为后缀,被称为“头文件”。

8.2、参数传递

参数传递和变量的初始化类似,根据形参的类型可以分为两种方式:传值(value)和传引用(reference)。

8.2.1、传引用参数

(1)传引用方便函数调用

C++新增了引用的概念,可以替换必须使用指针的场景。采用引用作为函数形参,可以使函数调用更加方便。这种传参方式叫做“传引用参数”。之前的例子就可以改写成:

#include<iostream>using namespace std;//使用引用作为形参
void increase(int& x)
{++x;
}int main()
{int num = 0;increase(num);cout << "num = " << num << endl;cin.get();
}

运行结果:

(2)传引用避免拷贝

使用引用还有一个非常重要的场景,就是不希望进行值拷贝的时候。实际应用中,很多时候函数要操作的对象可能非常庞大,如果做值拷贝会使得效率大大降低;这时使用引用就是一个好方法。

比如,想要定义一个函数比较两个字符串的长度,需要将两个字符串作为参数传入。因为字符串有可能非常长,直接做值拷贝并不是一个好选择,最好的方式就是传递引用:

8.2.2、数组形参

数组是不允许做直接拷贝的,所以如果想要把数组作为函数的形参,使用值传递的方式是不可行的。与此同时,数组名可以解析成一个指针,所以可以用传递指针的方式来处理数组。|

比如一个简单的函数,需要遍历int类型数组所有元素并输出,就可以这样声明:

#include<iostream>using namespace std;//遍历数组
void printArray(const int* arr, int size)
{for (int i = 0; i < size; i++){cout << arr[i] << "\t";}cout << endl;
}int main()
{int arr[5] = { 1,2,3,4,5 };printArray(arr, 5);cin.get();
}

运行结果:

8.2.3、数组引用作为形参

#include<iostream>using namespace std;//遍历数组
void printArray(const int* arr, int size)
{for (int i = 0; i < size; i++){cout << arr[i] << "\t";}cout << endl;
}void printArray(const int(& arr)[5])
{for (int num : arr){cout << num << "\t";}cout << endl;
}int main()
{int arr[5] = { 1,2,3,4,5 };printArray(arr, 5);printArray(arr);cin.get();
}

运行结果:

8.3.1、无返回值

#include<iostream>using namespace std;void swap(int& x, int& y)
{int temp = x;x = y;y = temp;
}int main()
{int a = 66, b = 88;swap(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;cin.get();
}

运行结果:

8.3.2、有返回值

#include<iostream>using namespace std;void swap(int& x, int& y)
{int temp = x;x = y;y = temp;
}//有返回值的函数,返回较长的字符串
string longstr(const string& str1, const string& str2)
{return str1.size() >= str2.size() ? str1 : str2;
}int main()
{int a = 66, b = 88;swap(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;string str1 = "hello";string str2 = "hello world";cout << longstr(str1, str2) << endl;cin.get();
}

运行结果:

8.3.3、返回数组指针

这里对于函数fun的声明,我们可以进行层层解析:

        fun(int x):函数名为fun,形参为int类型的x;

        (*fun(int 炳 ):函数返回的结果,可以执行解引用操作,说明是一个指针;

        ( *fun(int x))[5]:函数返回结果解引用之后是一个长度为5的数组,说明返回类型是数组指针;

        int ( * fun(int x) )[5]:数组中元素类型为int

九、递归

如果一个函数调用了自身,这样的函数就叫做“递归函数”(recursivefunction)。

1、递归的实现

递归是调用自身,如果不加限制,这个过程是不会结束的;函数永远调用自己下去,最终会导致程序栈空间耗尽。所以在递归函数中,一定会有某种“基准情况”,这个时候不会调用自身,而是直接返回结果。基准情况的处理保证了递归能够结束。|

递归是不断地自我重复,这一点和循环有相似之处。事实上,递归和循环往往可以实现同样的功能。

#include<iostream>using namespace std;//用递归实现阶乘
int factorial(int n)
{if (n == 1)return 1;return factorial(n - 1) * n;
}int main()
{cout << "5! = " << factorial(5) << endl;cin.get();
}

运行结果:

这里我们的基准情况是n ==1,也就是当n不断减小,直到1时就结束递归直接返回。5的阶乘具体计算流程如下:

因为递归至少需要额外的栈空间开销,所以递归的效率往往会比循环低一些。不过在很多数学问题上,递归可以让代码非常简洁。

2、经典递归-------斐波那契数列

#include<iostream>using namespace std;//用递归实现阶乘
int factorial(int n)
{if (n == 1)return 1;return factorial(n - 1) * n;
}//斐波那契数列
int fib(int n)
{if (n == 1 || n == 2)return 1;return fib(n - 1) + fib(n - 2);
}int main()
{cout << "5! = " << factorial(5) << endl;cout << "fib(5) = " << fib(5) << endl;cin.get();
}

运行结果:

十、函数高阶

1、内联函数

内联函数是C++为了提高运行速度做的一项优化

函数让代码更加模块化,可重用性、可读性大大提高;不过函数也有一个缺点:函数调用需要执行一系列额外操作,会降低程序运行效率。

为了解决这个问题,C++引入了“内联函数”的概念。使用内联函数时,编译器不再去做常规的函数调用,而是把它在调用点上“内联”展开,也就是直接用函数代码替换了函数调用。

1.1内联函数的定义

定义内联函数,只需要在函数声明或者函数定义前加上 inline关键字

1.2内联函数和宏

内联函数是C++新增的特性。在C语言中,类似功能是通过预处理语句和#define定义“宏”来实现的。

然而c中的宏本身并不是函数,无法进行值传递;它的本质是文本替换,我们一般只用宏来定义常量。用宏实现函数的功能会比较麻烦,而且可读性较差。所以在C++中,一般都会用内联函数来取代C中的宏。

2、函数重载

在C++中,同一作用域下,同一个函数名是可以定义多次的,前提是形参列表不同。这种名字相同但形参列表不同的函数,叫做“重载函数”。这是C++相对c语言的重大改进,也是面向对象的基础。

2.1定义重装函数

重载的函数,应该在形参的数量或者类型上有所不同;

形参的名称在类型中可以省略,所以只有形参名不同的函数是一样的;

调用函数时,编译器会根据传递的实参个数和类型,自动推断使用哪个函数;

主函数不能重载

2.2函数匹配

确定到底调用哪个函数的过程,叫做“函数匹配”。

#include<iostream>using namespace std;void f() { cout << "1" << endl; }
void f(int x) { cout << "2" << endl; }
void f(int x, int y) { cout << "3" << endl; }
void f(double x, double y = 1.5) { cout << "4" << endl; }int main()
{f(3.14);cin.get();
}

运行结果:

2.3函数重装和作用域

重载是否生效,跟作用域是有关系的。如果在内层、外层作用域分别声明了同名的函数,那么内层作用域中的函数会覆盖外层的同名实体,让它隐藏起来。

不同的作用域中,是无法重载函数名的

3、函数指针

一类特殊的指针,指向的不是数据对象而是函数,这就是“函数指针”

3.1声明函数指针

函数指针本质还是指针,它的类型和所指向的对象类型有关。现在指向的是函数,函数的类型是由它的返回类型和形参类型共同决定的,跟函数名、形参名都没有关系。

3.2使用函数指针

当一个函数名后面跟调用操作符(小括号),表示函数调用;而单独使用函数名作为一个值时,函数会自动转换成指针。这一点跟数组名类似。

所以我们可以直接使用函数名给函数指针赋值:

也可以加上取地址符&,这和不加&是等价的。

3.3函数指针作为形参

有了指向函数的指针,就给函数带来了更加丰富灵活的用法。比如,可以将函数指针作为形参,定义在另一个函数中。也就是说,可以定义一个函数,它以另一个函数类型作为形参。当然,函数本身不能作为形参,不过函数指针完美地填补了这个空缺。这一点上,函数跟数组非常类似。

3.4函数指针作为返回值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/37176.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring】DAO 和 Repository 的区别

DAO 和 Repository 的区别 1.概述2.DAO 模式2.1 User2.2 UserDao2.3 UserDaoImpl 3.Repository 模式3.1 UserRepository3.2 UserRepositoryImpl 4.具有多个 DAO 的 Repository 模式4.1 Tweet4.2 TweetDao 和 TweetDaoImpl4.3 增强 User 域4.4 UserRepositoryImpl 5.比较两种模式…

深度学习基准模型Mamba

深度学习基准模型Mamba Mamba(英文直译&#xff1a;眼镜蛇)具有选择性状态空间的线性时间序列建模&#xff0c;是一种先进的状态空间模型 (SSM)&#xff0c;专为高效处理复杂的数据密集型序列而设计。 Mamba是一种深度学习基准模型&#xff0c;专为处理长序列数据而设计&…

【鸿蒙学习笔记】位置设置

官方文档&#xff1a;位置设置 目录标题 align&#xff1a;子元素的对齐方式direction&#xff1a;官方文档没懂&#xff0c;看图理解吧 align&#xff1a;子元素的对齐方式 Stack() {Text(TopStart)}.width(90%).height(50).backgroundColor(0xFFE4C4).align(Alignment.TopS…

<Python><ffmpeg>基于python使用PyQt5构建GUI实例:音频格式转换程序(MP3/aac/wma/flac)(优化版2)

前言 本文是基于python语言使用pyqt5来构建的GUI,功能是使用ffmpeg来对音频文件进行格式转换,如mp3、aac、wma、flac等音乐格式。 UI示例: 环境配置 系统:windows 平台:visual studio code 语言:python 库:pyqt5、ffmpeg 概述 本文是建立在之前的博文的基础上的优化版…

在线教育项目(一):如何防止一个账号多个地方登陆

使用jwt做验证&#xff0c;使用账号作为redis中的key,登录的时候生成token放到redis中&#xff0c;每次申请资源的时候去看token 有没有变&#xff0c;因为token每次登录都会去覆盖&#xff0c;只要第二次登录token就不一样了

Day7:.翻转字符串里的单词 151 卡码网:55.右旋转字符串

题目 151. 反转字符串中的单词 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:// 移除多余空格void moveSpace(string& s) {// 定义快慢指针int slow 0;int fast 0;// 删除前导空格while (s.size() > 0 && fast < s.size() &&…

【算法——双指针前缀和】

例题&#xff1a; 奇偶排序数组&#xff08;与下标对应&#xff09; 奇数偶数个数相等 922. 按奇偶排序数组 II #include<iostream> #include<vector> #include<algorithm> using namespace std;int main() {vector<int>nums { 4,2,5,7 };//指针x…

==和equals的区别(面试题)

和equals有什么区别 对于基本数据类型&#xff0c;比较的是值是否相等&#xff0c;对于引用类型则是比较的地址是否相等&#xff1b;对于equals来说&#xff0c;基本数据类型没有equals方法&#xff0c;对于引用类型equals比较的是引用对象是否相同 那针对以上结论&#xff0c…

西点领导力:卓越是怎样练成的

今天刚看了一个美国西点军校第50任校长&#xff1a;罗伯克卡斯伦的《为什么跟西点军校学领导力培养》这个演讲。从中受益良多&#xff0c;于是我就去了解了一下这位校长以及西点军校。 西点军校 西点军校&#xff08;United States Military Academy, USMA&#xff09;&#…

Android常用加解密算法总结

Android开发中对于数据的传输和保存一定会使用加密技术&#xff0c;加密算法是最普遍的安保手段&#xff0c;多数情况数据加密后在需要使用源数据时需要再进行解密&#xff0c;但凡是都有例外。下面从可逆加密、不可逆、不纯粹加密三种方式记录一下常见的加解密算法。 加密技术…

【使用sudo apt-get出现报错】——无法获得锁 /var/lib/dpkg/lock-open(11:资 源暂时不可用) ,是否有其他进程正占用它?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ubuntu中进程正在被占用1. 问题描述2. 原因分析3. 解决 总结 前言 一、ubuntu中进程正在被占用 1. 问题描述 在Ubuntu中&#xff0c;使用终端时输入带有…

昇思25天学习打卡营第7天之二 | 模型保存与加载

1. 保存与加载 在训练网络模型的过程中&#xff0c;实际上我们希望保存中间和最后的结果&#xff0c;用于微调&#xff08;fine-tune&#xff09;和后续的模型推理与部署&#xff0c;本章节我们将介绍如何保存与加载模型。 1.1 导入依赖 # 导入numpy库&#xff0c;并将其重命…

六月,允许自己做自己,别人做别人

今天结束后&#xff0c;2024 就过去一半了。 年初的规划完成一半了吗&#xff1f;如果没有也没关系&#xff0c;做你自己继续前进。 家人来北京旅游&#xff0c;我累趴了 六月初&#xff0c;我搬家了&#xff0c;这次租了一整套房&#xff0c;是一个小俩居、还带一个小阁楼。…

数学学习与研究杂志社《数学学习与研究》杂志社2024年第6期目录

课改前沿 基于核心素养的高中数学课堂教学研究——以“直线与圆、圆与圆的位置关系”为例 张亚红; 2-4 核心素养视角下初中生数学阅读能力的培养策略探究 贾象虎; 5-7 初中数学大单元教学实践策略探索 耿忠义; 8-10《数学学习与研究》投稿&#xff1a;cn7kantougao…

使用Python绘制极坐标图

使用Python绘制极坐标图 极坐标图极坐标图的优点使用场景 效果代码 极坐标图 极坐标图&#xff08;Polar Chart&#xff09;是一种图表类型&#xff0c;用于显示在极坐标系中的数据。极坐标图使用圆形坐标系&#xff0c;角度表示一个变量的值&#xff0c;半径表示另一个变量的…

线程安全问题(二)——死锁

死锁 前言可重入锁逻辑 两个线程两把锁&#xff08;死锁&#xff09;死锁的特点多个线程多把锁&#xff08;哲学家就餐问题&#xff09;总结 前言 在前面的文章中&#xff0c;介绍了锁的基本使用方式——锁 在上一篇文章中&#xff0c;通过synchronized关键字进行加锁操作&am…

XML简介XML 使用教程XML的基本结构XML的使用场景

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

VMware每次打开网络设置都出现需要运行NetworkManager问题

每次打开都出现这个情况&#xff0c;是因为之前把NetworkManager服务服务关闭&#xff0c;重新输入命令&#xff1a; sudo systemctl start NetworkManager.service或者 sudo service network-manager restart 即可解决&#xff0c;但是每次开机重启都要打开就很麻烦&#xf…

【Chapter4】汇编语言及其程序设计,《微机系统》第一版,赵宏伟

一、汇编语言概述 **指令&#xff1a;**指使计算机完成某种操作的命令。 **程序&#xff1a;**完成某种功能的指令序列。 **软件&#xff1a;**各种程序总称。 **机器语言&#xff1a;**计算机能直接识别的语言。用机器语言写出的程序称为机器代码。 **汇编语言&#xff1…

Forecasting from LiDAR via Future Object Detection

Forecasting from LiDAR via Future Object Detection 基础信息 论文&#xff1a;cvpr2022paper https://openaccess.thecvf.com/content/CVPR2022/papers/Peri_Forecasting_From_LiDAR_via_Future_Object_Detection_CVPR_2022_paper.pdfgithub&#xff1a;https://github.co…