一、数组
数组(array)是一种数据格式,能够存储多个同类型的值。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
要创建数组,可使用声明语句。数组声明应指出以下三点:
♦ 存储在每个元素中的值的类型;
♦ 数组名;
♦ 数组中的元素数。
声明数组的通用格式如下:
typeName arryName[arrySize];
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8 * sizeof(int)),即其中所有的值在编译时都是已知的。具体地说,arraySize不能是变量,变量的值是在程序运行时设置的。然而,本章稍后将介绍如何使用new运算符来避开这种限制。
作为复合类型的数组
数组之所以被称为复合类型,是因为它是使用其他类型来创建的(C语言使用术语“派生类型”,但由于C++对类关系使用术语“派生”,所以它必须创建一个新术语)。不能仅仅将某种东西声明为数组,它必须是特定类型的数组。没有通用的数组类型,但存在很多特定的数组类型,如char数组或long数组。例如,请看下面的声明:
float loans[20];
loans的类型不是“数组”,而是“float数组”。这强调了loans数组是使用float类型创建的。
对于数组来说,访问数组的第一个元素,对应的数组下标是0;

1.数组初始化规则
1.只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
注意:大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。
2.初始化数组时,提供的值可以少于数组的元素数目:
double balance[5] = {1000.0, 2.0};
如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。
因此,将数组中所有的元素都初始化为0非常简单—只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可:
double balance[5] = {0};
3.如果初始化数组时方括号内([ ])为空,C++编译器将计算元素个数。如:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
通常,让编译器计算元素个数是种很糟的做法,因为其计数可能与您想象的不一样。例如,您可能不小心在列表中遗漏了一个值。然而,这种方法对于将字符数组初始化为一个字符串来说比较安全,很快您将明白这一点。如果主要关心的问题是程序,而不是自己是否知道数组的大小,则可以这样做:
int num_elements = sizeof(balance) / sizeof(double)
2.C++数组详解
1. 多维数组
如,下面的声明创建了一个三维 5 . 10 . 4 整型数组:
int threedim[5][10][4];
多维数组最简单的形式是二维数组。二维数组的理解可以参考X,Y坐标形式来理解,如 a[3][4]:
| Column 0 | Column 1 | Column 2 | Column 3 | |
|---|---|---|---|---|
| Row 0 | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
| Row 1 | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
| Row 2 | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
因此,数组中的每个元素是使用形式为 a[ i ][ j ] 的元素名称来标识的,其中 a 是数组名称,i 和 j 是唯一标识 a 中每个元素的下标。
初始化二维数组有两种方式:
1.内嵌 括号 {}
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
2.没有内嵌 括号 {}
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
两种方式是一样效果的。
2.C++ 指向数组的指针
数组名是指向数组中第一个元素的常量指针。因此,在下面的声明中:
double buff[50];
buff 是一个指向 &buff[0] 的指针,即数组 buff 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 buff 的第一个元素的地址:
double *p;
double buff[10];
p = buff;
使用数组名作为常量指针是合法的,反之亦然。
因此,*(buff+ 4) 是一种访问 buff[4] 数据的合法方式。
一旦您把第一个元素的地址存储在 p 中,您就可以使用 * p、* (p+1)、* (p+2) 等来访问数组元素。下面的实例演示了上面讨论到的这些概念:
#include <iostream>
using namespace std;
int main ()
{
// 带有 5 个元素的双精度浮点型数组
double buff[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p;
p = buff;
// 输出数组中每个元素的值
cout << "使用指针的数组值 " << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "*(p + " << i << ") : ";
cout << *(p + i) << endl;
}
cout << "使用 buff作为地址的数组值 " << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "*(buff+ " << i << ") : ";
cout << *(buff+ 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
3.C++ 传递数组给函数
C++ 中您可以通过指定不带索引的数组名来传递一个指向数组的指针。
C++ 传数组给一个函数,数组类型自动转换为指针类型,因而传的实际是地址。
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
方式 1
形式参数是一个指针:
void myFunction(int *param)
{
.
.
}
方式 2
形式参数是一个已定义大小的数组:
void myFunction(int param[10])
{
.
.
}
方式 3
形式参数是一个未定义大小的数组:
void myFunction(int param[])
{
.
.
}
实例
该函数把数组作为参数,同时还传递了另一个参数,根据所传的参数,会返回数组中各元素的平均值:
#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 << "平均值是:" << avg << endl; //输出214.4
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;
}
就函数而言,数组的长度是无关紧要的,因为 C++ 不会对形式参数执行边界检查。
4.C++ 从函数返回数组
C++ 不允许返回一个完整的数组作为函数的参数。
但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。
如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
int * myFunction()
{
.
.
}
另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
现在,让我们来看下面的函数,它会生成 10 个随机数,并使用数组来返回它们,具体如下:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// 要生成和返回随机数的函数
int * getRandom( )
{
static int r[10];
// 设置种子
srand( (unsigned)time( NULL ) );
for (int i = 0; i < 10; ++i)
{
r[i] = rand();
cout << r[i] << endl;
}
return r;
}
// 要调用上面定义函数的主函数
int main ()
{
// 一个指向整数的指针
int *p;
p = getRandom();
for ( int i = 0; i < 10; i++ )
{
cout << "*(p + " << i << ") : ";
cout << *(p + i) << endl;
}
return 0;
}
编译结果
624723190
1468735695
807113585
976495677
613357504
1377296355
1530315259
1778906708
1820354158
667126415
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504
*(p + 5) : 1377296355
*(p + 6) : 1530315259
*(p + 7) : 1778906708
*(p + 8) : 1820354158
*(p + 9) : 667126415
二、字符串
C++ 提供了以下两种类型的字符串表示形式:
♦ C 风格字符串
♦ C++ 引入的 string 类类型
1.C风格字符串
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。
字符串实际上是使用null 字符 \0 终止的一维字符数组。
因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 RUNOOB 的字符数多一个。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
依据数组初始化规则,可以把上面的语句写成以下语句:
char site[] = "RUNOOB";
以下是 C/C++ 中定义的字符串的内存表示:
其实,不需要把 null 字符放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 \0 放在字符串的末尾。
C++ 中有大量的函数用来操作以 null 结尾的字符串:
| 序号 | 函数&目的 |
|---|---|
| 1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
| 2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。连接字符串也可以用 + 号,例如: string str1 = “runoob”; |
| 3 | strlen(s1); 返回字符串 s1 的长度。 |
| 4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
| 5 | strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
| 6 | strstr(s1, s2);返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
实例
#include <iostream>
#include <cstring>
using namespace std;
int main ()
{
char str1[13] = "runoob";
char str2[13] = "google";
char str3[13];
int len ;
// 复制 str1 到 str3
strcpy( str3, str1);
cout << "strcpy( str3, str1) : " << str3 << endl;//输出 runoob
// 连接 str1 和 str2
strcat( str1, str2);
cout << "strcat( str1, str2): " << str1 << endl;//输出 runoobgoogle
// 连接后,str1 的总长度
len = strlen(str1);
cout << "strlen(str1) : " << len << endl;// 输出 12
return 0;
}
2.C++中的String类
C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。
我们将学习 C++ 标准库中的这个类,现在让我们先来看看下面这个实例:
现在您可能还无法透彻地理解这个实例,因为到目前为止我们还没有讨论类和对象。所以现在您可以只是粗略地看下这个实例,等理解了面向对象的概念之后再回头来理解这个实例。
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;//输出 str3 : runoob
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;//输出 str1 + str2 : runoobgoogle
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;//输出 str3.size() : 12
return 0;
}
这个实例,可以看到相比于C风格的字符串,str1 , str2,str3定义的类型是string类型,同时在输出时,直接 对 str1 + str2,进行操作,可以先简单理解为string 是一个==“库”==,可以直接进行调用。
关于string类的相关信息
ISO/ANSI C++98标准通过添加string类扩展了C++库,因此现在可以string类型的变量(使用C++的话说是对象)而不是字符数组来存储字符串。
string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用string类,必须在程序中包含头文件string。
string类位于名称空间std中,因此必须提供一条using编译指令,或者使用std::string来引用它。
string类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。
下面实例说明了string对象与字符数组之间的一些相同点和不同点。
#include <iostream>
#include <string>
using namespace std;
int main ()
{
char chr1[20];
char chr2[20]="abc";
string str1 ;
string str2 = "def";
cout << "Enter a kind of feline : " ;
cin >> chr1;
cout << "Enter another kind of feline : " ;
cin >> str1;
cout << "Here are some feline: \n";
cout << chr1 << " " << chr2 << " "
<< str1 << " " << str2 << endl;
cout << "The third letter in " << chr2 << " is "
<< chr2[2] <<endl;
cout << "The third letter in " << str2 << " is "
<< str2[2] <<endl;
return 0;
}
运行情况:
Enter a kind of feline : qwe
Enter another kind of feline : asd
Here are some feline:
qwe abc asd def
The third letter in abc is c
The third letter in def is f
从这个示例可知,在很多方面,使用string对象的方式与使用字符数组相同。
♦ 可以使用C-风格字符串来初始化string对象。
♦ 可以使用cin来将键盘输入存储到string对象中。
♦ 可以使用cout来显示string对象。
♦ 可以使用数组表示法来访问存储在string对象中的字符。
string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组:
string str1;
string str2="asd";
类设计让程序能够自动处理string的大小。例如,str1的声明创建一个长度为0的string对象,但程序将输入读取到str1中时,将自动调整str1的长度:
cin >> str1;
这使得与使用数组相比,使用string对象更方便,也更安全。
从理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体。
可以使用cin和运算符<<来将输入存储到string对象中,使用cout和运算符<<来显示string对象,其句法与处理C-风格字符串相同。
但每次读取一行而不是一个单词时,使用的句法不同,下面实例说明了这一点
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int main ()
{
char chr1[20];
string str1 ;
cout << "Length of string in chr before input: "
<< strlen(chr1) << endl;
cout << "Length of string in str before input: "
<< str1.size() << endl;
cout << "Enter a line of text : " ;
cin.getline(chr1,20);
cout << "You entered: " << chr1 << endl;
cout << "Enter another line of text : " ;
getline(cin,str1);
cout << "You entered: " << str1 << endl;
cout << "Length of string in chr after input: "
<< strlen(chr1) << endl;
cout << "Length of string in str after input: "
<< str1.size() << endl;
return 0;
}
运行情况:
Length of string in chr before input: 1
Length of string in str before input: 0
Enter a line of text : qwer
You entered: qwer
Enter another line of text : asdfgh
You entered: asdfgh
Length of string in chr after input: 4
Length of string in str after input: 6
在用户输入之前,该程序指出数组charr中的字符串长度为1。
这里要两点需要说明。首先,为初始化的数组的内容是未定义的;其次,函数strlen( )从数组的第一个元素开始计算字节数,直到遇到空字符。
对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度很可能与此不同。
另外,用户输入之前,str中的字符串长度为0。这是因为未被初始化的string对象的长度被自动设置为0。
下面是将一行输入读取到数组中的代码:
cin.getline(chr1,20);
这种句点表示法表明,函数getline( )是istream类的一个类方法(还记得吗,cin是一个istream对象)。正如前面指出的,第一个参数是目标数组;第二个参数数组长度,getline( )使用它来避免超越数组的边界。
下面是将一行输入读取到string对象中的代码:
getline(cin,str1);
这里没有使用句点表示法,这表明这个getline( )不是类方法。它将cin作为参数,指出到哪里去查找输入。另外,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小。
三、C++指针
学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的。
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
#include <iostream>
using namespace std;
int main ()
{
int var1;
char var2[10];
cout << "var1 变量的地址: ";//var1 变量的地址: 0xbfebd5c0
cout << &var1 << endl;
cout << "var2 变量的地址: ";//var2 变量的地址: 0xbfebd5b6
cout << &var2 << endl;
return 0;
}
1.什么是指针?
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。
就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号(*)是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
2.C++中使用指针
使用指针时会频繁进行以下几个操作:
♦ 定义一个指针变量
♦ 把变量地址赋值给指针
♦ 访问指针变量中可用地址的值
这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";//输出 Value of var variable: 20
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";//输出 Address stored in ip variable: 0xbfc601ac
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: "; //输出 Value of *ip variable: 20
cout << *ip << endl;
return 0;
}
3.C++指针详解
在 C++ 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C++ 程序员必须清楚的一些与指针相关的重要概念:
| 概念 | 描述 |
|---|---|
| C++ Null 指针 | C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。 |
| C++ 指针的算术运算 | 可以对指针进行四种算术运算:++、–、+、- |
| C++ 指针 vs 数组 | 指针和数组之间有着密切的关系。 |
| C++ 指针数组 | 可以定义用来存储指针的数组。 |
| C++ 指向指针的指针 | C++ 允许指向指针的指针。 |
| C++ 传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
| C++ 从函数返回指针 | C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
1.C++ Null指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include <iostream>
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ;//输出 ptr 的值是 0
return 0;
}
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。
然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,可以使用 if 语句,如下所示:
if(ptr) /* 如果 ptr 非空,则完成 */
if(!ptr) /* 如果 ptr 为空,则完成 */
因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。
很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。
2.C++ 指针的算术运算
指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、–、+、-。
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
ptr++
在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
♦ 递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中的数组地址
ptr = var;
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[MAX-1];
for (int i = MAX; i > 0; i--)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// 移动到下一个位置
ptr--;
}
return 0;
}
运行结果
Address of var[3] = 0xbfdb70f8
Value of var[3] = 200
Address of var[2] = 0xbfdb70f4
Value of var[2] = 100
Address of var[1] = 0xbfdb70f0
Value of var[1] = 10
♦ 指针的比较
指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:
#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
3.C++ 指针 vs 数组
指针和数组是密切相关的,事实上,指针和数组在很多情况下是可以互换的。
例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。请看下面的程序:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中的数组地址
ptr = var;
for (int i = 0; i < MAX; i++)
{
cout << "var[" << i << "]的内存地址为 ";
cout << ptr << endl;
cout << "var[" << i << "] 的值为 ";
cout << *ptr << endl;
// 移动到下一个位置
ptr++;
}
return 0;
}
运行结果
var[0]的内存地址为 0x7fff59707adc
var[0] 的值为 10
var[1]的内存地址为 0x7fff59707ae0
var[1] 的值为 100
var[2]的内存地址为 0x7fff59707ae4
var[2] 的值为 200
【注意】:指针和数组并不是完全互换的。例如,请看下面的程序:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++)
{
*var = i; // 这是正确的语法 对var[0]赋值0
var++; // 这是不正确的
}
return 0;
}
把指针运算符 * 应用到 var 上是完全可以的,但修改 var 的值是非法的。
这是因为 var 是一个指向数组开头的常量,不能作为左值。
由于一个数组名对应一个指针常量,只要不改变数组的值,仍然可以用指针形式的表达式。例如,下面是一个有效的语句,把 var[2] 赋值为 500:
*(var + 2) = 500;
4.C++ 指针数组
在我们讲解指针数组的概念之前,先让我们来看一个实例,它用到了一个由 3 个整数组成的数组:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++)
{
cout << "Value of var[" << i << "] = ";
cout << var[i] << endl;
}
return 0;
}
运行结果
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:
int *ptr[MAX];
在这里,把 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 ptr[" << i << "] = ";
cout << *ptr[i] << endl;
}
return 0;
}
运行结果
Value of ptr[0] = 10
Value of ptr[1] = 100
Value of ptr[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
5.C++ 指向指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。
指针的指针就是将指针的地址存放在另一个指针里面。
通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 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
6.C++ 传递指针给函数
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 :1294450468
能接受指针作为参数的函数,也能接受数组作为参数,如下所示:
#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
7.C++ 从函数返回指针
在C++从函数返回数组小节中,已了解C++ 中如何从函数返回数组,类似地,C++ 允许您从函数返回指针。为了做到这点,您必须声明一个返回指针的函数,如下所示:
int * myFunction()
{
.
.
}
另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。
现在,让我们来看下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
// 要生成和返回随机数的函数
int * getRandom( )
{
static int r[10];
// 设置种子
srand( (unsigned)time( NULL ) );
for (int i = 0; i < 10; ++i)
{
r[i] = rand();
cout << r[i] << endl;
}
return r;
}
// 要调用上面定义函数的主函数
int main ()
{
// 一个指向整数的指针
int *p;
p = getRandom();
for ( int i = 0; i < 10; i++ )
{
cout << "*(p + " << i << ") : ";
cout << *(p + i) << endl;
}
return 0;
}
运行结果
624723190
1468735695
807113585
976495677
613357504
1377296355
1530315259
1778906708
1820354158
667126415
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504
*(p + 5) : 1377296355
*(p + 6) : 1530315259
*(p + 7) : 1778906708
*(p + 8) : 1820354158
*(p + 9) : 667126415
四、数据结构(结构体)
C/C++ 数组允许定义可存储相同类型数据项的变量,但是结构是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
C++中的结构的可以满足要求(存储篮球运动员的信息)。结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据,这使得能够将有关篮球运动员的信息放在一个结构中,从而将数据的表示合并到一起。
struct inflatable
{
char name[20];
float volume;
double price;
};
关键字struct表明,这些代码定义的是一个结构的布局。标识符inflatable是这种数据格式的名称,因此新类型的名称为inflatable。这样,便可以像创建char或int类型的变量那样创建inflatable类型的变量了。接下来的大括号中包含的是结构存储的数据类型的列表,其中每个列表项都是一条声明语句。这个例子使用了一个适合用于存储字符串的char数组、一个float和一个double。列表中的每一项都被称为结构成员,因此infatable结构有3个成员。总之,结构定义指出了新类型(这里是inflatable)的特征。
定义结构后,便可以创建这种类型的变量了:
inflatable zhangsan;
inflatable lisi;
如果您熟悉C语言中的结构,则可能已经注意到了,C++允许在声明结构变量时省略关键字struct:
struct inflatable zhangsan; //在 c 中需要添加 struct
inflatable zhangsan; //在 c++ 中不需要添加 struct
在C++中,结构标记的用法与基本类型名相同。这种变化强调的是,结构声明定义了一种新类型。在C++中,省略struct不会出错。
由于 zhangsan 的类型是 inflatable ,因此可以使用成员运算符(.)来访问各个成员。例如,zhangsan .volume指的是结构的volume成员,zhangsan .price指的是price成员。
1.结构体的用法
#include <iostream>
#include <cstring>
using namespace std;
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 输出 Book1 信息
cout << "第一本书标题 : " << Book1.title <<endl;
cout << "第一本书作者 : " << Book1.author <<endl;
cout << "第一本书类目 : " << Book1.subject <<endl;
cout << "第一本书 ID : " << Book1.book_id <<endl;
// 输出 Book2 信息
cout << "第二本书标题 : " << Book2.title <<endl;
cout << "第二本书作者 : " << Book2.author <<endl;
cout << "第二本书类目 : " << Book2.subject <<endl;
cout << "第二本书 ID : " << Book2.book_id <<endl;
return 0;
}
实例中定义了结构体类型 Books 及其两个变量 Book1 和 Book2。当上面的代码被编译和执行时,它会产生下列结果:
第一本书标题 : C++ 教程
第一本书作者 : Runoob
第一本书类目 : 编程语言
第一本书 ID : 12345
第二本书标题 : CSS 教程
第二本书作者 : Runoob
第二本书类目 : 前端技术
第二本书 ID : 12346
2.结构体的初始化
和数组一样,使用由逗号分隔值列表,并将这些值用花括号括起。在该程序中,每个值占一行,但也可以将它们全部放在同一行中。只是应用逗号将它们分开:
inflatable duck = {"xiaoming" 0.12 9.98};
可以将结构的每个成员都初始化为适当类型的数据。例如,name成员是一个字符数组,因此可以将其初始化为一个字符串。
【注意】:在C++11中
与数组一样,C++11也支持将列表初始化用于结构,且等号(=)是可选的:
inflatable duck {"xiaoming" 0.12 9.98}; //在C++11中可以不加 = 号
其次,如果大括号内未包含任何东西,各个成员都将被设置为零。例如,下面的声明导致mayor.volume和mayor.price被设置为零,且mayor.name的每个字节都被设置为零:
inflatable mayor {};
最后,不允许缩窄转换。
《缩窄转换》
C++11中的列表初始化禁止缩窄转换,关于缩窄转换的规则如下:
1.从浮点数转换为整数
2.从取值范围大的浮点数转换为取值范围小的浮点数(在编译期可以计算并且不会溢出的表达式除外)
3.从整数转换为浮点数(在编译期可以计算并且转换之后值不变的表达式除外)
4. 从取值范围大的整数转换为取值范围小的整数(在编译期可以计算并且不会溢出的表达式除外)
3. 结构可以将string类作为成员吗
可以将成员name指定为string对象而不是字符数组吗?即可以像下面这样声明结构吗?
#include <string>
struct inflatable
{
std:string name;
float volume;
double price;
};
答案是肯定的,只要您使用的编译器支持对以string对象作为成员的结构进行初始化。
一定要让结构定义能够访问名称空间std。为此,可以将编译指令using移到结构定义之前;也可以像上面那样,将name的类型声明为std::string。
4. 其他结构属性
C++使用户定义的类型与内置类型尽可能相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算符(=)将结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。
1.成员赋值 & 创建结构
示例:
#include <iostream>
using namespace std;
struct inflatable
{
char name[20];
float volume;
double price;
};
int main( )
{
inflatable bounquet =
{
"sunflowers",
0.20,
12.49
};
inflatable choice;
cout << "bounquet :" << bounquet.name << "for $";
cout << bounquet.price <<endl;
choice = bounquet;
cout << "choice :" << choice.name << "for $";
cout << choice.price <<endl;
return 0;
}
运行结果
bounquet :sunflowersfor $12.49
choice :sunflowersfor $12.49
从中可以看出,成员赋值是有效的,因为choice结构的成员值与bouquet结构中存储的值相同。
可以同时完成定义结构和创建结构变量的工作。为此,只需将变量名放在结束括号的后面即可:
struct perks
{
int key_num;
char car[12];
}mr_smith,ms_jones;
甚至可以初始化以这种方式创建的变量:
struct perks
{
int key_num;
char car[12];
}mr_smith=
{
7,
"packcard"
};
然而,将结构定义和变量声明分开,可以使程序更易于阅读和理解。
还可以声明没有名称的结构类型,方法是省略名称,同时定义一种结构类型和一个这种类型的变量:
struct
{
int x;
int y;
}mr_smith;
这样将创建一个名为mr_smith的结构变量。可以使用成员运算符来访问它的成员(如mr_smith.x),但这种类型没有名称,因此以后无法创建这种类型的变量。
2.结构作为函数参数
您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用下面实例中的方式来访问结构变量:
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 输出 Book1 信息
printBook( Book1 );
// 输出 Book2 信息
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
cout << "书标题 : " << book.title <<endl;
cout << "书作者 : " << book.author <<endl;
cout << "书类目 : " << book.subject <<endl;
cout << "书 ID : " << book.book_id <<endl;
}
运行结果:
书标题 : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题 : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346
3.指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books *book );
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 通过传 Book1 的地址来输出 Book1 信息
printBook( &Book1 );
// 通过传 Book2 的地址来输出 Book2 信息
printBook( &Book2 );
return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
cout << "书标题 : " << book->title <<endl;
cout << "书作者 : " << book->author <<endl;
cout << "书类目 : " << book->subject <<endl;
cout << "书 ID : " << book->book_id <<endl;
}
运行结果
书标题 : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题 : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346
typedef 关键字
下面是一种更简单的定义结构的方式,您可以为创建的类型取一个"别名"。例如:
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
现在,您可以直接使用 Books 来定义 Books 类型的变量,而不需要使用 struct 关键字。下面是实例:
Books Book1, Book2;
也可以使用 typedef 关键字来定义非结构类型,如下所示:
typedef long int *pint32;
pint32 x, y, z;
x, y 和 z 都是指向长整型 long int 的指针。
5.结构数组
inflatable结构包含一个数组(name)。也可以创建元素为结构的数组,方法和创建基本类型数组完全相同。例如,要创建一个包含100个inflatable结构的数组,可以这样做:
inflatable gifts[100];
这样,gifts将是一个inflatable数组,其中的每个元素(如gifts[0]或gifts[99])都是inflatable对象,可以与成员运算符一起使用:
cin >> gifts[0].volume;
cout << gifts[99].price <<endl;
记住,gifts本身是一个数组,而不是结构,因此像gifts.price这样的表述是无效的。
6.结构中的位字段
与C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。
字段的类型应为整型或枚举(稍后将介绍),接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段(bit field)。
下面是一个例子:
struct torgle-register
{
unsigned int SN:4;// 4位表示SN值
unsigned int :4;//4位未使用
bool goodIn :1; //有效输入(1位)
bool goodTorgle :1;
};
五、共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构可以同时存储 int、long和double,共用体只能存储int、long 或double。共用体的句法与结构相似,但含义不同。例如,请看下面的声明:
union one4all
{
int int_val;
long long_val;
double double_val;
};
可以使用 one4all 变量来存储 int、long或double,条件是在不同的时间进行:
one4all pail;
pail.int_val = 5;
cout << pail.int_val;
pail.double_val= 1.52;
cout << pail.double_val;
因此,pail有时可以是int变量,而有时又可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度。
共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。
六、枚举
C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。它还允许定义新类型,但必须按严格的限制进行。使用enum的句法与使用结构相似。例如,请看下面的语句:
enum spectrum {red,orange,yellow,green,blue,violet,indigo,ultraviolet};
这条语句完成两项工作。
♦ 让spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
♦ 将red、orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫作枚举量(enumerator)。
在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。可以通过显式地指定整数值来覆盖默认值,本章后面将介绍如何做。
可以用枚举名来声明这种类型的变量:
spectrum band;
枚举变量具有一些特殊的属性,下面来看一看。
在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量,如下所示:
band = blue;//有效的,blue是 enumeration
band = 2000;//无效的
因此,spectrum变量受到限制,只有8个可能的值。如果试图将一个非法值赋给它,则有些编译器将出现编译器错误,而另一些则发出警告。为获得最大限度的可移植性,应将把非enum值赋给enum变量视为错误。
枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型:
int color =blue;//有效的
band =3; //无效的
color = 3 + red;//有效的
虽然在这个例子中,3对应的枚举量是green,但将3赋给band将导致类型错误。不过将green赋给band是可以的,因为它们都是spectrum类型。同样,有些实现方法没有这种限制。
表达式3 + red中的加法并非为枚举量定义,但red被转换为int类型,因此结果的类型也是int。由于在这种情况下,枚举将被转换为int,因此可以在算术表达式中同时使用枚举和常规整数,尽管并没有为枚举本身定义算术运算。
1.设置枚举量的值
可以使用赋值运算符来显式地设置枚举量的值:
enum bits{one = 1, two=2, four = 4, eight = 8};
指定的值必须是整数。也可以只显式地定义其中一些枚举量的值:
enum bigstep{first, second=100, third};
这里,first在默认情况下为0。后面没有被初始化的枚举量的值将比其前面的枚举量大1。因此,third的值为101。
最后,可以创建多个值相同的枚举量:
enum {zero, null = 0, one, umero_uno=1};
其中,zero和null都为0,one和umero_uno都为1。在C++早期的版本中,只能将int值(或提升为int的值)赋给枚举量,但这种限制取消了,因此可以使用long甚至long long类型的值。
2.枚举的取值范围
最初,对于枚举来说,只有声明中指出的那些值是有效的。然而,C++现在通过强制类型转换,增加了可赋给枚举变量的合法值。每个枚举都有取值范围(range),通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量,即使这个值不是枚举值。例如,假设bits和myflag的定义如下:
enum bits{one = 1, two=2, four = 4, eight = 8};
bits myflag;
则下面的代码将是合法的:
myflag = bits{6};
其中6不是枚举值,但它位于枚举定义的取值范围内。
取值范围的定义如下:
首先,要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。例如,前面定义的bigstep的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号。例如,如果最小的枚举量为−6,而比它小的、最大的2的幂是−8(加上负号),因此下限为−7。
选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间;而对于包含long类型值的枚举,则使用4个字节。
下一篇:五、C++ 类、对象、继承
本文详细介绍了C++中的数组,包括其声明、初始化规则、多维数组和数组与指针的关系。接着探讨了C++中的字符串,包括C风格字符串和C++的string类。指针部分讲解了指针的概念、指针算术运算、指针数组、指向指针的指针以及指针在函数中的使用。此外,还讨论了结构体,包括结构体的声明、初始化、作为函数参数以及结构体与数组的结合。最后,提到了共用体和枚举,解释了它们的用途和特性。
549

被折叠的 条评论
为什么被折叠?



