C++第六章数组、指针与字符串
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:学习笔记
第六章 数组、指针与字符串
6.1 数组
数组是具有一定 顺序关系 的若干 相同类型变量的集合体 ,组成数组的变量称为该数组的元素。
数组的定义:
类型说明符 数组名[常量表达式] [常量表达式]…;
例如:int a[10]
表示a为整型数组,有10个元素:a[0]…a[9]
int a[5][3]
表示a为整型二维数组,其中第一维有5个下标(0~4),第二维有3个下标,数组的元素个数为15,是5行3列的整型数据。
下面这个代码展示了数组的两种输出方式:
#include<iostream>
using namespace std;
int main(){
int a[10];
int b[10];//没有初始化,数组中的值不确定
for(int i = 0; i < 10; i++){
a[i] = i *2 -1;
b[10 - i -1] = a[i];
}
for(const auto &e:a){ //范围for循环
cout << e << " ";
}
cout << endl;
for(int i = 0; i < 10; i++){ //下标迭代循环
cout << b[i] << " ";
}
cout << endl;
}
一维数组的初始化
//第五章讲过,static变量如果你不初始化,它会自动初始化为0
//但是如果不说明是static,比如上面代码中,没有初始化,数组中的值不确定
static int a[10]
//列出全部元素的初始值
static int a[10] = {0,1,2,3,4,5,6,7,8,9};
//只给一部分元素指定初值,后面没有被初始化的元素会自动是0
int a[10] = {0,1,2,3,4};
//在列出全部数组元素初值时,可以不指定数组长度
static int a[] = {0,1,2,3,4,5,6,7,8,9};
二维数组存储
假设定义二维数组:float a[3][4];
a[0]代表第一行
a[1]代表第二行
数组a的存储顺序是按行展开的

二维数组的初始化
和上面的一维数组初始化一样:如果不做任何初始化,局部作用域的非静态数组中的值是不确定的,static数组中的数据默认是0
如果只对部分元素初始化,剩下的未显式初始化的元素,将自动被初始化为0
//所有初值都写在一个{}内,按顺序初始化
static int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//分行列出二维数组元素的初值
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
//可以只对部分元素初始化
static int a[3][4] = {{1},{0,6},{0,0,1,1}};
//在列出全部数组元素初值时,第1维下标个数可以省略
static int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
static int a[][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
使用数组作为函数参数
例6-2主函数中初始化一个二维数组,表示一个矩阵,并将每个元素都输出,然后调用子函数,分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主函数之后输出各行元素的和。(为什么要存在第一行的第一个元素啊,很奇怪,另外定义一个sum数组,存在这里面,函数rowSum输入的形参为int a[][4], int nRow, int sum[])
#include<iostream>
using namespace std;
void rowSum(int a[][4], int nRow, int s[]){ //注意这里传入的参数形式
for(int i = 0; i < nRow; i++){
for(int j = 1; j < 4; j++){
s[i] += a[i][j];
}
}
}
int main(){
int table[3][4] = {1,2,3,4,5,6,7,8,9,2,1,1};
// int n = sizeof(a) / sizeof(a[0]); // 记录行数
// int m = sizeof(a[0]) / sizeof(a[0][0]); //记录列数
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
cout << table[i][j] << " ";
}
cout << endl;
}
cout << endl;
int sum[3] = {0};
rowSum(table, 3, sum);
for(int i = 0; i < 3; i++){
cout << sum[i] << " ";
}
cout << endl;
return 0;
}
这个代码其实不太好,后面学了vector来写会更好一些。主要是了解一下数组做形参传入什么形式吧
对象数组的定义与访问
定义对象数组: 类名 数组名[元素个数]
访问对象数组元素:数组名[下标].成员
对象数组初始化:
数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象
通过初始化列表赋值:
例如:Point a[2] = {Point(1,2), Point(3,4)}
如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默认构造函数)
上面这段话看完就是云里雾里的,下面仔细地解释一下:
假设你有一个类 Point:
class Point {
public:
int x, y;
Point(int a=0, int b=0) : x(a), y(b) {} // 构造函数
};
如果是:Point a[2];,这里a是一个长度为2的Point 对象数组(看清是对象数组),每一个元素都会调用默认构造函数(也就是Point(int a=0, int b=0))所以:
a[0]被初始化为(0,0)
a[1]被初始化为(0,0)
这是对象数组默认初始化地方式。
如果使用初始化列表:Point a[2] = {Point(1,2), Point(3,4)};,a有2个元素,对应列表中的两个对象:
a[0] 调用 Point(1,2) 构造函数 → 初始化为 (1,2)
a[1] 调用 Point(3,4) 构造函数 → 初始化为 (3,4)
所以这个写法就是显式地为数组里地每一个对象调用构造函数并赋初值。
如果列表中元素少于数组长度,其余元素会调用默认构造函数,如果多于数组长度,会报错。
6.2 指针
C++中可以通过变量名或者通过地址来访问内存空间。
地址运算符:&
例如:int var
&var表示取变量var首字节的地址
这个符号&我们前面也见到过,首先它可以当二元运算符,表示按位与运算,还有第三章中这个符号表示引用,在本章它表示取这个变量的首字节地址
指针的概念
指针:内存地址,用于间接访问内存单元
指针变量:用于存放地址的变量(搞清楚哦,定义一个指针类型的变量里面存放的是地址而不是变量的内容)
下面这个图片中完整并且清楚的写明了指针的声明、定义和引用,static int i;声明一个静态整数变量i,static int *ptr = &i;是声明一个静态指针ptr,ptr被初始化为指向i的地址,从右边的图可以看出,它存储的是i的首字节地址2000(假设i的首字节地址是2000),指针本身也是一个变量,它也有自己的地址(图上假设是3010)
下面的两个引用,i=3;是通过变量名直接赋值,*ptr=3不是把 3 存进指针,而是通过指针找到 i 的内存位置,然后把 3 存进 i 中,所以改变的是 i 的值,而不是ptr本身,ptr仍然存储着 i 的地址。
总之:指针变量ptr本身也是一个变量,它占用内存,存储的是 i 的地址,当我们写*ptr=3时,我们是通过ptr存储的地址来修改 i 的值,而不是修改ptr本身,ptr本身存储的地址值并没有改变。

指针变量的赋值运算
向指针变量赋的值必须是地址常量或变量,不能是普通整数:
int *ptr = &i; - 给指针变量本身赋值
*ptr = 3; - 通过指针给指向的变量赋值
通过地址运算“&”求得已定义的变量和对象的起始地址:int *ptr = &i;
但是如果:int *ptr = 2000这样会报错,不能给指针变量本身赋为普通整数
动态内存分配成功时返回的地址
例外:整数0可以赋值给指针,表示空指针:in *ptr = 0;
允许定义或声明指向void类型的指针,该指针可以被赋予任何类型对象的地址:void *general;
指针空值nullptr:
以往用0或者NULL表达空指针
在C++11中,使用nullptr关键字,作为空指针,但是如果你习惯用0也可以
指向常量的指针:
不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象
const定义了p1是只读的,刚开始你可以让p1指向a的地址,然后又改为指向b的地址,这是可以改变的,但是你不能通过p1指向的地址中存入值,只能通过变量名赋值,比如b=6:
int a;
const int *p1 = &a; //p1是指向常量的指针
int b;
p1 = &b; //正确,p1指向b
*p1 = 1; //错误,不能通过p1改变所指的对象的值
b = 6; //正确,通过变量名改变b的值
指针类型的常量:
若声明指针常量,则指针本身的值不能被改变:
const距离p2更近了,说明p2本身是一个常量,固定指向a,如果后面再改为指向b,是不允许的,因为p2本身的值是不能改变的,别忘记了p2中存放的是地址,你存放的是a的地址,就不能再改变了:
int a;
int *const p2 = &a; //固定指向a,不能再改变
*p2 = 1;//这是可以的
int b;
p2 = &b; //错误,p2是指针常量,值不能改变
指针的算术运算
指针与整数的加减运算
指针++、--运算
指针p加上或减去n:
指针当前指向位置的前方或后方第n个数据的起始位置。
指针的++、--运算:
指向下一个或前一个完整数据的起始
运算的结构取决于指针指向的数据类型,总是指向一个完整数据的起始位置
当指针指向连续存储的同类型数据时,指针与整数的加减运算和自增自减才有意义。
下面的图上的代码:short *pa = a而不能写成short *pa = &a:
因为在初始化指针pa时,我们想要的是指向数组第一个元素的指针,所以应该使用数组名a(它退化为首元素指针)或者显式地使用&a[0],如:short *pa = &a[0];与图片上第二行代码等价。
&a是指向整个数组的指针,会报错,类型不匹配,也就是说&a代表整个数组,这个数组长度为4,每个里面存的值类型是short。short *pa 声明了一个指向单个short的指针。

6.2.6用指针处理数组元素
定义指向数组元素的指针:
例如:int a[10], *pa;
pa = &a[0];或 pa=a;
经过上述定义及赋值后:
*pa就是a[0],*(pa+1)就是a[1],*(pa+i)就是a[i];
a[i]、*(pa+i)、*(a+i)、pa[i]就是等效的
后面做到动态内存分配的时候,分配一个数组,没有名字只有首地址,到时候会用到用指针访问数组,所以接下来用三种方法遍历数组:
#include<iostream>
using namespace std;
int main(){
int a[10] = {1,2,3,4,5,6,7,8,9,0};
for(int i = 0; i < 10; i++){ //第一种,用数组下标遍历数组
cout << a[i] << " ";
}
cout << endl;
for(int i = 0; i < 10; i++){ //第二种,用数组名和指针运算遍历数组
cout << *(a+i) << " ";
}
cout << endl;
for(int *p = a; p < (a+10); p++){ //第三种,用指针变量遍历数组
cout << *p << " ";
}
cout << endl;
return 0;
}
上面说的三个方法有一个问题,就是你要知道这个数组中由多少个元素,这样就比较容易出错,在C++11中,定义了两个标准库函数begin和end,这两个函数将数组作为它们的参数:
int a[10] = {1,2,3,4,5,6,7,8,9,0};
int *beg = begin(a);
int *last = end(a);
begin函数返回指向数组a首元素的指针,end函数返回指向a尾元素下一位置的指针,这两个函数定义在iterator头文件中。
下面代码找到数组arr中的第一个负数:
//pbeg指向数组的首元素,pend指向数组尾元素的下一位置
int *pbeg = begin(arr), *pend = end(arr);
while(pbeg != pend && *pbeg >= 0){
++pbeg;
}
如果pend指向的元素是负数,退出循环,否则,pbeg指针向前移动一位考察下一个元素。
6.2.7 指针数组
我们知道,指针也是一种数据类型,数组的每个元素也可以是指针,这就是指针数组。
指针数组有很多用途,下面举个例子,用指针数组存放二维矩阵,后面第八章多态性就会用到指针数组。
指针数组:
数组的元素是指针型:例如:Point *pa[2];(指向对象的指针构成数组,由pa[0],pa[1]两个指针组成)
如果定义一个二维矩阵,需要进行多次换行,就是行与行之间交换,怎么办比较好?
分别定义三个一维数组,每个一维数组代表一个行,把每个一维数组的首地址放进指针中,如果交换0行和1行,那么交换指针0和指针1即可:

#include<iostream>
using namespace std;
int main(){
int line1[] = {1,0,0}; //矩阵的第一行
int line2[] = {0,1,0}; //矩阵的第二行
int line3[] = {0,0,1}; //矩阵的第三行
//定义整型指针数组并用这三个一维数组的数组名(也就是数组首元素的地址)初始化这个指针数组
int *pLine[3] = {line1, line2, line3};
//输出矩阵
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
cout << pLine[i][j] << " ";
}
cout << endl;
}
return 0;
}
上面的程序就是利用指针数组存放矩阵,并输出这个矩阵,那么问题来了,为什么要写两个for循环,而不是一个for循环,然后输出pLine[i],因为pLine是一个数组啊,总共三个元素,每个元素代表矩阵的一行,我输出指针数组的三个元素不就是三行矩阵吗?
我笑死,看似合理的逻辑,其实并不是的,你再看看上面的图片,pLine[1]并不是代表矩阵的第一行啊,里面存放的是第一行的首地址,存放的是地址!!
for(int i = 0; i < 3; i++){
cout << pLine[i] << " ";
}
如果你运行上面这个代码,运行输出的是三个地址:
0x70fe30 0x70fe20 0x70fe10
这样输出的实际上是指针数组每个元素的值,也就是三个地址,因为pLine[i]是指针(地址),而不是整个一行矩阵。
原代码:
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
cout << pLine[i][j] << " ";
}
cout << endl;
}
这里,pLine[i]是一个指针,指向第i行的第一个元素。然后通过pLine[i][j]来访问第i行的第j个元素。
实际上,pLine[i][j]等价于*(pLine[i] + j),也就是先取得第i行的首地址,然后偏移j个int单位,再取该位置的值。
更多推荐



所有评论(0)