C++第三章: 数组与文字处理

一、数组
    1.1 定义数组
    1.2 访问数组元素:
    1.3 数组的整体输入和输出
    1.4 数组的初始化
    1.5 常用的数组处理方法
二、指针与数组
    2.1 通过指针变量间接访问数组元素
    2.2 指针变量的算术运算
    2.3 指针变量之间的关系运算
    2.4 指针的取内容运算
    2.5 动态内存分配
        1、基本的定义和释放方法
        2、一维数组的动态分配与释放
三、字符类型
    3.1 字符集
    3.2 字符型运算
四、字符数组与文字处理
    4.1 字符串常量
    4.2 字符数组
    4.3 字符数组的整体输入/输出
    4.4 指针变量输出
五、中文处理
    5.1 字符编码标准
    1、基于ANSI的中文处理程序
    2、基于Unicode编码的中文处理


数据需要保存再内存中才能被计算机读取,计算结果也只能保存在计算机内存中,如果程序需要处理大量的数据,如何定义变量呢?例如要处理100个同学的成绩单。

未了处理和保存大规模的数据,C++语言提供了特殊的语言组织形式,我们称之为数组。

数组的基本概念:

数组是一组数据类型相同,变量某种次序排列的数据集合,其中的每个数据被称为数组的一个元素,数组元素按排列次序编号,编号为整数,从0开始,编号就被称为数组元素的下标。存储一维方向排列的数据,例如数列,就是用一维数组,一维数组有一个下标;存储二维方向排列的数据,例如矩阵,就是用二维数组,二维数组有2个下标,第一个为行下标,第二个为列下标;c++语言也可以定义多维数组,最常用的就是一维数组和二维数组。

文字处理程序处理字符数据:

文字程序所处理的对象是字符数据,计算机只能存储和处理数值数据,而文字是程序所处理的对象是字符数据,为了存储和处理字符数据,需要使用某种字符标准,将字符转换为字符编码,字符编码就是字符的编码,例如计算机可以处理英文,ASCII码就是专门为处理英文设计的编码标准,c++语言使用字符类型来存储字符的编码,该变码是一个单字节整数,c++语言将字符类型和单字节整数类型合二为一,统称为字符类型,关键字是char,在计算机内部,一个字符就是一个单字节整数,一个字符类型的变量可以保存一个字符,而如果需要保存一篇文章,这篇文章其实是一组字符,我们就需要使用字符数组

一、数组

1.1 定义数组

语法:

数据类型 数组变量名[常量表达式1][常量表达式2]...[常量表达式n]

详解:

举例:

int x[5]; //定义一个一维数组变量x,包含5个元素
double y[2+3]; //定义一个double类型一维数组变量y,包含5个元素
int z[5][10]; //定义一个int类型的二维数组变量z,包含5行,10列,共50个元素。
int a,x[5],z[5][10]; //可以将相同类型的数组变量和普通变量放在一条语句定义 

当执行数组类型语句时,计算机为数组变量中的所有元素同时分配内存空间,各数组元素的内存单元,在内存中是按顺序连续排列的,数组变量所占字节数等于所有元素所占字节数的总和,为了方便程序员,c++语言提供了一种sizeof运算符,来求某种数据类型或变量所占用的字节数。

sizeof运算符:

sizeof(数据类型名)
或
sizeof(表达式)

语法说明:

举例:

sizeof(int); //结果为4,因为int类型占用4个字节
sizeof(5); //结果为4,因为5是int型。
sizeof(2+3); //结果为4,因为5是int型。

int a[5];
sizeof(a); //因为a是一个int型一维数组变量,有5个元素,一个元素占用4个字节,故结果为20。

1.2 访问数组元素:

遍历:对数据集合最常规的处理方法是依次访问集合中的每个元素,将所有的元素逐个处理一遍,这种处理方法称为对数据集合的遍历**

设计遍历算法,需要用到循环结构

数组变量名[下标表达式1][下标表达式2]...[下标表达式n]

语法说明:

举例:定义一个一维数组变量:int x[3];

int x[3]; 
/*访问x中的元素*/
x[0] = 10; //将数组变量x的第0个元素赋值为10
x[1] = 20; //将数组变量x的第1个元素赋值为20
x[2] = 30; //将数组变量x的第2个元素赋值为30
x[3] = 40; //越界错误,x的上界为2

实例:二维数组的访问

如果定义了二维数组y

int y[2][3]; //包含2行3列,共计6个元素

y[0][0] = 10; //将数组第0行,第0列赋值为10
y[0][1] = 20; //将数组第0行,第1列赋值为20

y[1][2] = 30; //将数组第1行,第3列赋值为20
//访问二维数组时,行下标和列下标,均不能越界

放位数组元素时,可以使用变量,或表达式来指定数组元素的下标,举例:

int n=0,x[3];
x[n] = 10; //n=0,故将x数组中的第0个元素赋值为10
x[n+1] = 20; //n+1=1,故将x数组中的第1个元素赋值为10
//使用变量和表达式作为下标访问数组的元素时,同样不能越界。

1.3 数组的整体输入和输出

可以使用cin语句一次性从键盘输入所有的数组元素,这种方式称为数组的整体输入,也可以用cout语句将数组元素输出到显示器上,我们称之为数组的整体输出,数组的整体输入输出,需要遍历所有的数组元素,可以使用循环结构来实现。

#include <iostream>
using namespace std;
int main(){
    int m,n;
    int x[3];
    
    for(n=0;n<3;n++)        // 因当注意n的取值范围,避免越界错误
        cin>>x[n];
    
    for(n=0;n<3;n++)
        cout<<"一维数组y:"<<x[n]<<",";
    cout<<endl;
    double y[2][3];
                            /*访问二维数组,需要用到2层循环*/
    for(m=0;m<2;m++)        //第一层是对行进行循环
    {
        for (n=0;n<3;n++)   //第二层是对列进行循环
            cin>>y[m][n];   //注意顺序,不能写反
    }
    for(m=0;m<2;m++)
    {
        for(n=0;n<3;n++)
            cout<<"二维数组y:"<<y[m][n]<<",";
        cout<<endl;
    }
    return 0;
}

1.4 数组的初始化

定义一个数组变量时,各数组元素都被分配在内存单元,但内存单元存放的是以前遗留的数据,这些数据是不确定的,在定义数组变量时,可以为全部或部分元素赋初始值,这就称为数组变量的初始化。

初始值用大括号{}括起来,二维数组的初始值的排列顺序是按照先行后列的顺序。

int x[3] = {2,4,6}; //将3个元素的初始值依次设定为2、4、6
/*将第0行的3个元素依次设定为1、3、5  将第1行的3个元素依次设定为2、4、6*/
double y[2][3] = {1,3,5,2,4,6}; 

定义数组变量时,如果列出了全部元素的初始值,那么可以省略第一个下标,计算机可以根据已经初始化的元素个数自动计算第一个下标的数量。

int x[]={2,4,6}; //一维数组省略下标个数,将根据初始值数量把下标个数设定为3
double y[][3] = {1,3,5,2,4,6}; //多维数组只能省略第一个下标个数,将根据初始值数量将行下标个数设定为2,6/3=2

当给出的初始值个数少于下标数量时,则会依次赋值,最后没有被赋值的元素,自动设定为0,这种只给出部分初始值的方法,称为部分初始化,部分初始化时,未被初始化的元素自动设为0.

int x[3]={2,4}; //依次将第0个元素和第一个元素赋值为2和4,第三个元素没有初始值,设为0.
double y[2][3]={{1,3},{2,4}};//每一行最后一个元素没有初始值,设定为0
double y[2][3]={{1,3,5}};//第0行的3个元素设定为1、3、5,第1行没有给出初始值,3个元素设定为0

1.5 常用的数组处理方法

数组变量中存放的是一个数据集合,处理数据集合算法,例如求数组元素的总和,或平均值,通常都要遍历数组中的所有元素,因此需要使用循环结构来实现。遍历多维数组时需要用到多重循环。

实例:求数组元素总和以及平均值的c++程序

#include <iostream>
using namespace std;
int main(){
    float y[2][3] = {{1.0,3.5,5.2},{2.2,4.9,6.5}};
    int m,n;
    float average,sum=0;
/*
    求二维数组的总和和平均值,需要先遍历行,在遍历列。
*/
    for(m=0;m<2;m++)
    {
        for(n=0;n<3;n++)
            sum +=y[m][n]; //将数组y中所有的元素全部累加到sum上
    }
    average = sum/6;
    cout<<"数列的元素的和为:"<<sum<<"平均值为:"<<average<<endl;
    return 0;
}

实例:求二维数组元素中的最大值和最小值

#include <iostream>
using namespace std;
int main(){
    int x[2][3] = {{1,3,5},{2,4,6}};
    int m,n;
    int max=x[0][0],min =x[0][0]; //先假定第一个元素是最大或最小的
    /*利用循环对所有值进行判断*/
    for(m=0;m<2;m++)
    for(n=0;n<3;n++)
    {
        if(x[m][n]>max) max=x[m][n];
        if(x[m][n]<min) min=x[m][n];
    }
   cout<<"最大值是:"<<max<<",最小值是:"<<min<<endl;
    return 0;
}

数组排序:

排序是将原来无序的数据集合按照某种规则进行排序,这样可以提高今后查找的速度。

假如定义一个数组变量x:int x[6] = {1,4,6,2,5,3};该集合的数据顺序是杂乱无章的,如果按照数据从小到大排序,即可得到一个按照1、2、3、4、5、6排列的一维数组。选择排序:

#include <iostream>
using namespace std;
int main(){
    int x[6]={1,3,4,6,5,2};
    int a,b,min,temp;
    for(a=0;a<6-1;a++) //选取0-4的元素,剩余一个不选择
    {
        min=a; //此时假设下标为a的元素就是最小的,将下标a赋值给中间变量min
        for(b=a+1;b<6;b++) //第二次循环从1-5,也就抽取是第一次循环下标a后面的数
        {
            if(x[b]<x[min]) //通过下标抽取元素,并比较这两个数的大小
            min=b; //如果下标b所对应的元素更小,将b的值赋值给min
        }
    temp=x[a];x[a]=x[min];x[min]=temp; //再次通过一个中间变量交换下标a和下标min所对应的元素值
    }
    for(a=0;a<6;a++)
    cout<<x[a]<<",";
    return 0;
}

二、指针与数组

定义数组变量,计算机将为数组变量中的所有元素同时分配内存空间,各数组元素的内存单元在内存中是按顺序连续排列的,本章讲述通过指针变量访问元素的方法。

2.1 通过指针变量间接访问数组元素

定义一个和数组相同类型的指针变量,就可以将指针变量指向任意一个数组元素,然后通过指针变量间接访问数组元素。

实例:星号*是取内容运算符

int x[6] = {1,4,6,2,5,3};
int *p; //定义一个和数组x相同类型的指针变量p,类型都是int型
p = &x[0]; //将指针变量指向数组x的第0个元素,第0个元素的地址是该数组的首地址
cout<<*p; //通过指针变量访问第0个元素,结果为1
p = &x[1]; //修改指针变量指向,将指针变量指向第1个元素

数组第0个位置的地址,我们称之为首地址,可以直接通过数组名来取出数组的首地址。

p = &x[0]; //将指针变量p指向数组的首地址
p = x; //也可以通过访问数组名的方式取得一维数组的内存首地址。

可以将一维数组的数组名理解为是一个表示该数组首地址的符号常量

2.2 指针变量的算术运算

利用数组元素在内存中连续存储的特点,利用加减运算,修改地址值,可以让指针变量指向不同的数组元素,根据不同符号类型的元素存储宽度,对指针变量值进行加减操作即可,例如一个int型的数组,每个元素的存储位数是4个字节,要改变指针变量的指向, 只需要对指针变量执行加减4即可。例如:

也就是说程序员使用指针变量,遍历数组,在将指针变量从一个元素移到下一个元素时,要考录到数组的类型,不同的类型增加不同的字节数,这样是比较麻烦的,为了方便程序员使用指针变量遍历数组,减轻程序员的负担,C++语言规定,不管数组是什么类型,将指针变量从一个元素移动到下一个元素,都统一成:p+=1;为了实现这一点,c++修改了指针变量于整数加减的规则。

指针变量与整数进行加减运算:

假设指针变量的数类型为T,n为整数,表达式p\pm n的结果仍为T类型的指针,其地址值等于p的地址值:\pm n*sizeof(T),其中sizeof(T),就是T类型所占的字节数,按照这个运算规则,如果指针变量p指向数组对应的某个元素,那么p\pm n就是该元素后,第n个元素的地址,而p-n就是p目前指向的元素前面第n个元素的地址。

同类型指针变量之间的相减:

假设两个指针变量p1和p2他们的指针类型都为T类型,那么表达式p1-p2的结果为int型,数值等于:(p1-p2)/sizeof(T),就是两个地址的差值除以sizeof(T),就是T类型的字节数。按照这个运算规则,如果指针变量分别指向同一数组中的两个元素,则p1-p2的结果,就是这两个元素下标的差值

void类型指针:

c++规定不能参与上述运算,因为,sizeof(void)没有确切的定义

实例:

#include <iostream>
using namespace std;
int main(){
    int x[6]={1,4,6,2,5,3};
    int *p, *p1, *p2; //定义3个int型指针变量p,p1,p2,类型和数组类型相同
    p = &x[0]; //将指针变量p指向第0个元素
    for(int n=0;n<6;n++)
    cout<<*(p+n)<<","; //通过指针变量与整数的算术运算依次访问各数组元素
    cout<<endl;
    int d; //定义一个int型变量d
    p1 = p+1; //将指针变量p1指向第1个元素
    p2 = p+4; //将指针变量p2指向第4个元素
    d = p2-p1; //指针变量之间的差值等于等于其所指向数组下标之间的差值,及4-1
    cout<<d<<endl;//显示结果为3
    d = &x[4]-&x[1]; //数组元素地址之间的差值等于其下标之间的差值,及4-1
    cout<<d<<endl; //显示结果为3
    return 0;
}

2.3 指针变量之间的关系运算

使用关系型运算符,可以比较两个地址值的大小,如果两个指针变量指向同一数组中的元素,则可以通过比较他们的大小,确定其指向数组元素之间的位置次序关系,地址值小的元素在前,地址值大的元素在后,如果相等,则说明这两个指针变量指向了数组中的同一个元素。

实例:

#include <iostream>
using namespace std;
int main(){
    int x[6]={1,4,6,2,5,3};
    int *p;
    for(p=&x[0]; p<=&x[5]; p++)//确定循环的终止点,比较指针p和&x[5]的大小。
    cout<<*p<<","; //通过*p取出内存中对应的值,*号是取内容运算符
    cout<<endl;
    return 0;
}

2.4 指针的取内容运算

指针运算符星号*,也被称为取内容运算符,通过指针变量保存的内存地址,访问所指向的内存单元,

int x[6] = {1,4,6,2,5,3};
*p=x; //定义指针变量p指向数组x的首地址,也就是&x[0]
/*通过指针运算符*访问数组元素*/
*p; //访问第0个元素
*(p+1); //访问第1个元素
*(p+2); //访问第2个元素
/*通过下标运算符[],来访问数组元素*/
p[0]; //访问第0个元素
p[1]; //访问第1个元素
p[2]; //访问第2个元素

一维数组的数组名可以理解为一个表示该数组首地址的符号常量。因此数组名除了使用下标运算符[]来访问数组元素外,也可以用指针运算符来访问数组元素。因此数组名也可以认为是一个指针

int x[6] = {1,4,6,2,5,3};
/*通过下标运算符[],来访问数组元素*/
x[0]; //访问第0个元素
x[1]; //访问第1个元素
x[2]; //访问第2个元素
/*通过指针运算符*访问数组元素*/
*x; //访问第0个元素
*(x+1); //访问第1个元素
*(x+2); //访问第2个元素

通过以上几种访问形式,可以看出,从本质上讲,访问数组元素,是一种通过指针的间接访问,访问时,可以使用下标运算符[],也可以使用指针运算符*。

2.5 动态内存分配

程序定义数组变量可以保存大量相同类型的数据,在处理不同数据的时候,数组的元素个数往往不一样,定义数组时,元素个数该定义为多少,需要程序员在定义数组时先预估元素的个数,如果程序员在定义数组时数组元素少于实际的元素数量,就会造成错误,多于实际的元素个数时,就会造成内存的浪费,在c++语言中可以使用动态内存分配的方法。

动态内存分配:

new和delete运算符,new分配内存,delete运算符用于释放内存。动态内存分配应当在使用完以后,及时释放内存,以提高内存的利用率,内存的动态分配、访问和释放都必须通过指定变量才能实现。针对单个变量和数组变量,new和delete运算符的语法不一样。

动态内存分配又称为在堆分配,生存期由用户指定,分配灵活,但有内存泄露等问题,是一个在程序里面随机申请的内存。

在C语言中使用函数来实现内存的动态分配

C++语言提供了另外一种新的动态内存分配方法

动态内存分配可以提高内存的使用率,内存的动态分配、访问和释放,都需通过指针变量来实现,C++针对单个变量和数组变量,new运算符和delete运算符使用语法有区别

1、基本的定义和释放方法

动态内存分配时,不需要指定变量名,但是需要指定数据类型,分配成功后,将返回分配内存单元的首地址,需要预习定义好一个同类型的指针变量来保存这个地址,后续访问该内存单元时,需要使用指针变量,来进行间接访问,释放内存单元时也需要使用这个指针变量,来指定释放哪个内存单元,

指针变量名 = new 数据类型(初始值);
delete指针变量名;

语法说明:

int *p; //为了动态分配一个int型变量,需预先定义好一个int型指针变量
p = new int; //使用new运算符动态分配一个int型变量,将所分配内存单元的首地址赋值给指针变量p
*p = 10; //通过指针变量p间接访问所分配的内存单元,向其中写入数据10
cout<<*p; //通过指针变量p间接访问所分配的内存单元
delete p; //内存使用完后,用delete运算符释放该内存空间

上述语句可以简化为:

int *p = new int(10); //动态分配变量时进行初始化
cout<<*p; //通过指针变量p间接访问所分配的内存单元
delete p; //内存使用完后,用delete运算符释放该内存空间

2、一维数组的动态分配与释放

C++语法:

指针变量名 = new 数据类型[整数表达式];
delete[]指针变量名;

语法说明:

int *p = new int[5]; //动态分配一个int型一维数组变量,包含5个数组元素
*(p+1) = 10; //通过指针运算符访问第1个元素,向其中写入数据10
p[1] = 10   //或通过下标运算符访问第1个元素
cout << *(p+1); //通过指针运算符访问第1个元素,读取其中数据10并显示出来
cout<<p[1]; //或通过下标运算符访问第1个元素
delete[]p; //内存使用完后,用delete运算符释放该数组变量所分配的内存空间,将内存空间全部释放

实列:计算斐波那契额数列

#include <iostream>
using namespace std;
int main(){
    int N; //定义一个int变量N
    cin >> N; //键盘输入要显示数列的前多少项,将数值保存在变量N中
    int *p = new int[N]; //动态创建包含N个元素的数组,用于保存数列的前N项
    p[0] = 0; p[1] = 1; //指定数列的前2项
    int n; //为循环语句定义好循环变量n
    for(n=2; n<N;n++) //使用循环结构计算处剩余的数列项
        p[n] = p[n-1] + p[n-2];//每一项等于其前2项之和
        for(n=0; n<N;n++) //使用循环结构遍历显示数组
    {
        cout << p[n] << ","; //各数列项用逗号隔开
        if((n+1)%5 == 0) cout<<endl; //一行显示5项,每5项一次换行
    }
    delete[]p; //数组使用结束,动态释放其内存空间
    return 0;
}

三、字符类型

计算机只能储存和处理数值类型的数据,为了进行文字处理,计算机需要讲文字字符转换成字符编码,这样文字处理问题,就转换成了数值计算问题,字符编码需要遵循统一的标准,需要考虑以下两个方面的内容:

3.1 字符集

首先要确定有那些字符,然后确定每个字符的编码值,早期的计算机只考虑英文处理,因此ASCII字符编码只收录了阿拉伯数字、英文字母、常用符号、控制字符等英文处理要用到的字符共计128个,分别用0-127来表示这128个字符,可以用一个字节,就是8位来储存一个字符编码,ASCII编码的特点如下:

c++语言通过字符类型来存储字符编码,这个编码是一个单字节整数,c++语言讲字符类型和单字节整数类型合二为一,统称为:char类型,占用1个字节。char更多的时候都是用来保存字符的,因此将char类型称为字符型。一个字符型变量可以保存一个字符,保存的是该字符的ASCII码值。

字符型常量:

指的是某个特定的字符,用单引号‘ ’括起来,例如:'a'、'?'等,

char ch;
ch = 'M';//等价于:ch=77,因为大写字母M的ASCII值等于77

但是在ASCII字符集中含有33个不可见的控制字符,它们无法用单引号的形式来书写,例如Esc键(ASCII=27),在C++语言中可以使用16进制或8进制的ASCII值来书写这些不可见的字符常量,书写时需要用到转义字符反斜杠\

/*例如Esc键,ASCII=27*/
char ch;
ch = '\x1B'; //Esc的ASCII码16进制为1B
ch = '\33'; //Esc的ASCII码8进制为33

这种书写字符常量的方式,我们称为转义字符,反斜杠是转义字符的标记.。可见字符也可以使用转义的形式进行书写,例如'M',转义形式:'\x4D'、'\115',为了方便程序员,c++语言还预定义了一些常用的转义字符常量:

转移字符 含义 注释
\0 空字符 ASCII码值:0
\a 响铃 ASCII码值:7
\b 退格 ASCII码值:8
\t 水平制表符 ASCII码值:9
\n 换行,等价于endl ASCII码值:10
\v 垂直制表符(打印机有效) ASCII码值:11
\r 回车 ASCII码值:13
\' 单引号 被赋予了特殊含义,需转义恢复其原来含义
\" 双引号 被赋予了特殊含义,需转义恢复其原来含义
\\ 反斜杠 被赋予了特殊含义,需转义恢复其原来含义

3.2 字符型运算

可以对字符型数据进行算术运算,运算时,将字符的ASCII码值作为整数参数参与运算。

#include <iostream>
using namespace std;
int main(){
    char ch='A';
    for(int n=1; n<=26; n++) //利用循环结构,显示26个英文字母
    {
        cout << ch; //根据ch中的ASCII值,将结构打印出来
        ch++; //将保存的ASCII值加1,显示下一个字母
    }
    cout << "\n"; //等价于cout<<endl;
    return 0;
}

可以对字符型数据进行关系运算,运算时将字符的ASCII码值作为整数进行比较,比较其大小,其实是在比较其ASCII码值的大小。

实例

#include <iostream>
using namespace std;
int main(){
    char ch;
    cout << "请输入:";
    cin >>ch;
    if(ch>='0'&&ch<='9')
    cout << "您输入的是数字"<< endl;
    else if(ch>='a' && ch<='z')
    cout<<"您输入的是小写字母"<< endl;
    else if(ch>='A'&&ch<='Z')
    cout<<"您输入的是大写字母"<< endl;
    else
    cout<<"您输入的是其他键"<< endl;
    cout<<"该字符的ASCII值="<<(int)ch<<endl;
    return 0;
}

四、字符数组与文字处理

一个字符变量可以保存一个字节,而要保存一片文字就是一组字符,我们就需要使用字符类型的数组变量,简称字符数组

一个单词,一句话,一篇文章都是一种由字符组成的序列,我们称为字符串

c++语言使用字符数组来保存字符串,对字符串的处理主要包括:复制、连接、插入和删除等等。

双引号“ ”括起来的字符序列,被称为字符串常量,例如:“china”、“P”等等,计算机会为c++程序中的字符串常量分配内存空间,并自动在末尾添加控制符0作为结束标记,存储一个字符串常量所需内存空间的字节数等于字符个数加一,就是加一个结束标记。例如“china”,总共5个字符,加上末尾的控制符0,共计6个字节来储存china。

在c++语言中单引号‘ ’和双引号“ ”是不同的概念,单引号只能括起单个字符,表示字符常量,包含多个字符的字符串常量必须使用双引号。单个字符加上双引号会被当做字符串进行处理。例如:“A”,在存储时会在末尾加上控制符0,占用2个字节的内存。

4.1 字符串常量

可以将一个字符串常量赋值给一个字符型指针变量,其含义是将字符串常量在内存单元中的首地址赋值给字符型指针变量。

char *p;
p = "china"; //将china在内存中的首地址赋值给p,或者说p指向china在内存中的首地址

字符串常量可以包含转义字符,c++语言中,反斜杠,单引号,双引号,被赋予了特殊含义,如果我们需要显示这些符号,需要在前面添加反斜杠。举例:

cout<<"Yes\nNo"; //输出结果为:Yes换行No
cout<<"\"Yes\",\'No\'"; //输出为:"Yes",'No'

4.2 字符数组

可以定义字符数组来保存字符串,定义字符数组时,可以使用字符常量初始化数组元素,

char str[10] = {'C','h','i','n','a'};//使用'C','h','i','n','a'初始化前5个常量,剩余元素自动初始化为'0'
char str[] = {'C','h','i','n','a'};//c++会自动将下标设为5

定义字符数组时,若没有给出下标的值,c++语言会根据初始值的个数,自动给出下标。定义字符数组时,也可以使用字符串常量来初始化元素。

char str[10]="china"; //使用字符串初始化字符数组的前5个元素
char str[]="china";//此时下标自动设为6,因为字符串常量有一个结束的下标0
char str[3][10]={"Tom","John","Mary"};

4.3 字符数组的整体输入/输出

通常,cin和cout只能输入/输出单个元素,因此数组整体输入输出需要使用循环结构逐个输入/输出各数组元素。

但c++语言对cin/cout指令做了特殊处理,对字符型数组可以直接整体输出/输出,从而简化了字符数组的输入输出。

char str[10];cin>>str; //可以使用cin一次性整体输入str,而不使用循环
cout<<str;

需要注意的是,在键盘输入时不能超过字符数组定义的长度,否则会出现越界错误。输出时,从第0个字符开始依次输出,到空字符时结束,空字符是字符串结束的标记。

4.4 指针变量输出

如果指针变量保存了另外某个变量的地址,可以用cout来显示该地址值,但是对字符型指针变量,cout语句会有一些不同的处理,实例如下:

/*通过cout输出int型指针变量时,输出的时指针变量指向的变量内存地址的值*/
int x,*p=&x;cout<<p<<endl; //此时结果显示的是x的内存地址
/*通过cout输出字符型指针变量时,输出的结果是该字符型指针变量指向的字符串的值*/
char str[10]="China";
char *p = str;
cout<<p<<endl; //显示结果:China
cout<<p+2<<endl; //显示结果:ina
cout<<(int *)p<<endl;//强制转换字符型指针变量为int型指针变量,即可得到该变量所指向的内存地址值

通过cout输出字符型指针变量时,输出的结果是该字符型指针变量指向的字符串的值。如果想显示字符型指针变量中保存的地址值,需要将字符型指针强制转换为其他类型指针。

实例:求字符串中字符的长度

#include <iostream>
using namespace std;
int main(){
    char str[10]="China";
    int n=0;
    while(str[n]!='\0') //通过循环结构,从0开始判断是否是结束符'\0'        
	n++; //如果不是结束符'\0',将下标加一
    cout<<n<<endl;
    return 0;
}

实例:在字符串中插入字符串

#include <iostream>
using namespace std;
int main(){
    char str[10]="Chna";
    char ch='i'; //要插入的字符
    char oldch; //该变量的作用是保存上一次时,撤入位置后面的字符串
     int n=2; //确定要插入的位置
    do
    {
        oldch=str[n];//把当前位置的原来的字符先保存起来,以便后移
        str[n]=ch; //将ch保存到当前位置
        ch = oldch; //将oldch保存的字符转存到ch中,在一次循环时进行插入
        n++; //转入下一位置,循环插入
    }while(ch!='\0'); //若ch保存的值是结束符,停止操作
    str[n]='\0'; //为插入操作后的字符串添加结束符'\0' 
    cout<<str<<endl; //显示结果为China    return 0;}

实例:字符串拷贝

#include <iostream>
using namespace std;
int main(){
    char str1[10]="China";
    char str2[20];//要考入的字符数组要大于原数组,否则会导致越界错误
    int n=0;
    while (str1[n]!='\n')
    {
        str2[n]=str1[n];//拷贝第n个元素
        n++; //下标加1,继续拷贝下一个元素
    }
    str2[n]='\0';//为字符串str2添加结束符'\0'
    cout<<str2<<endl;//显示拷贝的字符串
    return 0;
}

五、中文处理

计算机要处理中文,要满足3个方面的条件,

  1. 汉字字符编码标准,例如GB2312

    为处理中文,我们首先要制定汉字字符处理标准,GB2312就是我们国家制定的国标。

  2. 需要一个支持中文编码标准的操作系统,例如中文Windows。

    中文操作系统应该能够提供汉字输入法,以及显示或者打印用的汉字库。

  3. 需要支持中文处理的中文应用软件,例如中文word。

    用户使用应用软件来处理文字。

前两项是中文处理的基础,有了这个基础,程序员才能编写中文处理软件。程序员在使用c++语言编写程序时,可以在c++语言中直接编写中文字符串常量。

演示程序:

#include <iostream>
using namespace std;
int main(){
    char str1[]="China"; //英文字符串
    char str2[]="中国"; //中文字符串
    char str3[]="China,中国"; //中英文混合
    cout<<str1<<endl;
    cout<<str2<<endl;
    cout<<str3<<endl;
    return 0;
}   

中文字符串和英文字符串在使用上没有什么区别,但是由于汉字字符串的编码标准与英文字符不同,汉字字符在存储和算法上,也会因此有所不同,为了编写中文处理软件,程序员需要深入理解汉字字符的编码方法。

5.1 字符编码标准

ASCII码是一个种英文的编码标准,ASCII的字符集包含以英文字母为主的128个文字符号,只能用于英文处理。为了处理本国语言,各个国家和地区分别制定了本国,或本地区的编码标准。

汉字编码首先要确定有哪些汉字字符,及字符集,然后再确定每个汉字字符的编码值,ASCII使用单字节也就是8位编码,只有256个码值,最多只能为256个字符编码,而汉字有几万个字符,常用的有数千个,因此汉字编码需要使用双字节,也就是16位编码,理论上双字节编码可以提供6万多个字符编码,

日文和韩文和中文的处理方式类似,国际上通常将这3种文字称为CJK,就是中文、日文、韩文的首字母缩写。

ANSI编码:

操作系统提供文字处理相关的输入法,编码,显示和打印字符的相关基础功能,通常,操作系统可以同时处理英文,和一种非英语文字,例如中文版windows可以同时处理中文和英文。英文符号使用单字节ASCII编码,而非英文符号,则分别使用各国自己制定的双字节编码标准,这种混合编码标准被称为ANSI编码

Unicode编码:

基于ANSI编码标准的计算机存在一个缺陷,就是只能英文可以和其他语种混用,因此开发多语种软件时,需要开发时,需要分别开发多个语种的不同版本,例如中文版、日文版、俄语版等等。为了解决这个问题,相关国际组织制定了新的统一的编码标准,称为Unicode编码标准。该编码标准将世界上主要的语言文字合在一起,构建了一个大的字符集,然后统一进行编码,可以实现多语种混用,未来开发软件,应当基于Unicode编码标准进行开发,这样可以更方便的推广到全球市场。

一个计算机程序是否支持中文处理,有2层含义:

  1. 是否具有适合中国用户的中文界面
  2. 是否需要处理中文,例如对中文字符串的插入、删除等操作。

为了编写中文处理软件,程序员需要深入理解汉字字符的编码标准,为了处理中文,首先需要建立中文文字的处理标准,中文编码标准首先要确定有哪些字符,就是字符集,然后再确定每个字符的编码值,1980年中国国家标准总局,发布了GB2312标准,共计6763个常用汉字和一些图形符号,1995年,全国信息技术标准化委员会对该编码标准进行了扩充,制定了汉字内码扩展规范,也就是目前使用的GBK编码,共计21000个汉字和图形符号,同时还收录了藏文、蒙文、维吾尔文等少数民族文字,现在的Windows操作系统都至此GBK编码,某些嵌入式系统可能还在使用GB2312标准。

基于ANSI标准的中文Windows操作系统包含2套字符集,一套是ASCII码字符集,采用单字节存储,码值范围是0-127,ASCII只使用了7位,因此再存储ASCII码时字节的最高位都为0,另外一套字符集就是GBk编码字符集,它使用双字节存储,其中第一个字节称为前导字节,存储时这个字节的最高位都为1,c++程序可以根据字节的最高进行判断字符的类型,最高位为0,表示为ASCII码字符,是1则表示是GBK字符。

1、基于ANSI的中文处理程序

在c++语言种,中文字符串和英文字符串在以下几个方面是完全一样的:

c++语言只支持双引号括起来的中文字符串常量,不支持单引号括起来的字符串常量,例如:"中"是正确的,但是‘中’,这种写法是非法的。

实例:筛选中文字符串

#include <iostream>
using namespace std;
int main(){
    char str[20];
    cout << "请输入一个中英文混合的字符串长度不能超过20个:";
    cin >> str;
    char cstr[20];  //用于保存筛选的字符串,长度必须大于或等于输入的字符串,避免越界错误
    int n,ch;       //n表示str的下标,ch表示cstr的下标
    n=0;ch=0;
    while(str[n]!='\0')
    {
        if((str[n]& 0x80) != 0) //通过位与运算来检查当前字节最高位是否为0,
                                //0x80掩码是一个最高位为1的掩码,其他7位为0。
        {
            cstr[ch] = str[n] ;cstr[ch+1] = str[n+1];   //因为中文字符存储为双字节,
                                                        //故要复制2个相邻字节的数据
            ch+=2; n+=2;        //下标加2,转到下一个字符元素。
        }
        else
            n++;                //若不是中文字符,就执行n+1的操作,因为英文字符存储占1个字节
    }
    cstr[ch] = '\0';
    cout << "您输入的复合字符串中包含的中文有:"<<cstr<<endl;
    return 0;
}

2、基于Unicode编码的中文处理

Unicode字符集也叫通用字符集,目前已经收录超过10万个字符,Unicode编码中的ASCII码值是一样的,但是其中包含的GBK码值不一样。不同的操作系统存储一个Unicode字符所需要的位数是不一样的,例如32位的Windows操作系统和VC 6.0存储的一个Unicode字符需要2个字节,而Linux操作系统和java语言需要4个字节,这是因为他们对Unicode编码实现方式不同,Unicode编码主要有3种实现方式:utf-8、utf-16、utf-32,其中utf表示Unicode的转换个数, 而8、16、32表示储存位数。

utf-32:位使用4个字节储存Unicode编码,utf-32格式是定长编码,简单直接,但占用储存空间多

utf-8:格式,将Unicode编码划分为4个空间,分别进行再编码,最后形成一种变长编码,其长度从1-4个字节不等,使用utf-8编码,储存一个英文字符只需呀一个字节,而汉字字符,可能需要3-4个字节,这种变长编码会增加文字算法的复杂性,比如插入和删除操作。

utf-16:是一种介于utf-8和utf-32之间的一种格式,不管中文还是英文,储存一个字符都是2个字节,这是一种定长编码,基于定长编码的文字处理算法比较简单,VC 6.0使用的是utf-16格式,来储存Unicode编码。

c++语言将Unicode编码称为宽字符类型,并专门定义了wchar_t关键字,在vc 6.0中,宽字节字符类型占用2个字节,并按无符号的格式储存Unicode编码,c++语言用大写的字母'L',来指定宽字符类型常量和宽字符串常量,类如:'a'是一个字符串常量,而L'a'是一个宽字符常量,L'中国'是一个宽字符串常量。编译程序时,vc 6.0编译器会将宽字符常量转换为uft-16格式,并取宽字符常量和宽字符串常量的结束控制符也是2个字节,也就是两个'\0'。

语法:

wchar_t wch=L'中'; //占用2个字节。
wchar_t wstr[]=L"中国abc"; //此时占用12个字节,无论英文、中文还是结束控制符都占用2个字节,编译后下标格式自动设为6

基于定长编码的文字处理算法比较简单,

实例:删除字符串中某个字符

#include <iostream>
#include <locale.h>
#include <locale>
#include <io.h>
#include <fcntl.h>
using namespace std;
int main(){
    wchar_t wstr[20]=L"中国abc";
    int n; //定义号wste所需的下标,变量位n
    n = 2; //删除数组wstr中的字符‘a’,其下标为2
    while(wstr[n] != L'\0') //宽空字符L'\0'时宽字符的结束标记
    {
        wstr[n] = wstr[n+1]; //将其后面的字符往前移一位
        n++; //下标加1,转到下一个字符
    }
    _setmode(_fileno(stdout), _O_U16TEXT);
    wcout<<wstr<<endl; //显示宽字符串时改用wcout指令
    return 0;
}

wcout:

输出宽字符串需改用wcout指令,wcout指令在显示中文时,首先将Unicode编码转换为GBK编码,然后再显示GBK编码的中文字符。使用wcout指令前,需将语言设置为中文简体,即GBK编码。

wcin

输入宽字符串需要改用wcin指令,wcin指令再输入中文时,先转换成GBK编码,然后将GBK编码转换成Unicode编码,使用wcout指令前,需将语言设置为中文简体,即GBK编码。