Cpp:C风格字符串

前面的内容我们使用过字符串字面值,并了解字符串字面值的类型是字符常量的数组,现在可以更加明确的认识到:字符串字面值的类型就是 const char 类型的数组。C++从C继承下来的一种通用的结构是C风格字符串(C-Style character string),字符串字面值就是该类型的实例。实际上,C风格字符串既不能确切的归结为C语言类型,也不能归结为C++的类型,而是以空字符 null 结束的字符数组:

char c1[] = {'C', '+', '+'};        //c1 维数为3,not c-style
char c2[] = {'C', '+', '+', '\0'};    //c2 维数为4
char c3[] = "C++";                    //c3 维数为4,自动添加空字符
const char *cp = "C++";        //自动添加空字符
char *cp1 = c1;        //指向c1数组的第一个元素,not c-style
char *cp2 = c2;        //指向c1数组的第一个元素,以空字符 `null` 结束的字符数组

c1cp1 都不是C风格字符串:c1 是不带结束符 null 的字符数组。其他都是C风格字符串。

1、C风格字符串的使用


C++通过 (const) char* 类型的指针来操作C风格的字符串。

const char *cp = "C++";
while (*cp) {
    cout << *cp << endl;
    ++cp;
}

输出

C
+
+

如果cp指向的字符数组没有 null 结束符,作为循环会失败。这时循环继续进行,知道遇到内存中某处的 null 结束符。

2、C风格字符串的标准库函数


下表类除了C语言标准库函数提供的一系列处理C风格字符串的库函数,这些库函数包含在 string.h 中。

库函数 含义
strlen(s) 返回s的长度,不包括null结束符
strcmp(s1, s2) 比较s1和s2是否相同。s1等于s2,返回0;s1大于s2,返回正数;s1小于s2,返回负数
strcat(s1, s2) 将字符串s2连接到s1后面,返回s1
strcpy(s1, s2) 将s2复制给s2,返回s1
strncat(s1, s2, n) 将s2的前n个字符连接到s1后面,并返回s1
strncpy(s1, s2, n) 将s2的前n个字符赋给s1,并返回s1

传递给这些函数的指针必须具有非零值,并且指向以null结束的字符数组中的第一个元素。并且要确保目标字符串必须有足够大的空间存放结果串。

字符串的比较和比较结果都必须使用标准库函数 strcmp 进行

const char *cp1 = "C++";
const char *cp2 = "C";
int i = strcmp(cp1, cp2);
cout << i << endl; 
i = strcmp(cp2, cp1);
cout << i << endl;
i = strcmp(cp1, cp1);
cout << i << endl;

输出

1
-1
0

不要忘记字符串结束符null

char c[] = { 'c' };
cout << strlen(c) << endl;

输出结果是不可预料的,因为会一直寻找null结束符,然后再输出。比如在我的电脑是输出是 324,这明显是错误的,在不同的电脑会输出不同的值。

必须确保目标字符串有足够的大小

库函数 strcat 和 strcpy 的第一个参数必须有足够大的空间

const char *cp1 = "C++";
const char *cp2 = "C";
char str[3 + 1 + 2];
strcpy(str, cp1);
strcat(str, " ");
strcat(str, cp2);
cout << str << endl;

输出

C++ C

上述程序就目前来看是完全没问题的,但如果 cp1 和 cp2 指向的字符串大小发生了改变,str 所需的大小就不满足要求了。会导致严重的安全漏洞。

strn函数处理C风格的字符串

如果必须处理C风格字符串,strncat,strncpy会比strcat,strcpy更安全

char str[3 + 1 + 2];
strncpy(str, cp1, 4);    //1. 包含null
strncat(str, " ", 2);    //2. 包含null,看起来冗余,但是个好习惯
strncat(str, cp2, 2);    //3. 要包含null
cout << str << endl;

分步骤解释:

    1. strncpy cp1 时需要复制 4 个字符:cp1 中所有字符,加上 null 结束符;此时 strlen(str) = 3.
    1. strncat " " 时需要复制 2 个字符:一个空格字符,加上 null 结束符;此时空格符会把步骤1复制的 null 结束符覆盖。此时 strlen(str) = 4.
    1. strncat cp2 时需要复制 2 个字符:cp2 中所有字符,加上 null 结束符;此时 strlen(str) = 6.

最后 str 的内容:cp1 和 cp2 中所有字符,一个空格,和一个null结束符。

尽可能使用string类型

string str = cp1;
str += " ";
str += cp2;

可以达到上面一样的效果。

3、创建动态数组


数组类型的限制:长度不变;编译时须知道长度;数组只在定义它的语句内存在。可以使用动态分配解决这一问题,跟C中的malloc和free类似,C++中使用 newdelete 实现。

3.1、定义

数组变量需要指定类型、数组名和维数定义,而动态分配的数组只需指定类型和长度,不必为数组对象命名。

int *ip = new int[10];

3.2、初始化

动态分配的数组,如果有类型,则使用类型的默认构造函数初始化;如果是内置类型,则无初始化:

string *sp = new string[10];    //初始化为10个空字符串
int *ip = new int[10];        //不初始化
int *ip2 = new int [10]();    //初始化为10个0

圆括号要求编译器对数组进行初始化;
动态分配的数组,其元素只能初始化为默认值,而不能像变量一样提供初始化列表进行初始化

3.3、const对象的动态数组

必须为该数组提供初始化值,因为数组内都是const对象,无法赋值

const int *caip_bad = new const int[10];    //error
const int *caip_ok = new const int[10]();    //ok

也可以定义类类型的const数组,但该类型必须提供默认构造函数

 const string *csp = new const string[10];    //ok

3.4、动态分配空数组

有时候,编译时并不知道数组的长度,这时可以动态分配空数组。看如下程序

size_t n = get_size();
int *p = new int[n];
for (int *q = p; q != p + n; ++q)
    //process the array

即使 get_size() 返回 0 也是可以的。例如

char arr[0];    //error
char *cp = new char[0];    //ok,但不能被解引用

上述 cp 指针 允许 的操作有:比较;本身加(减)0。

3.5、动态空间释放

动态分配的空间必须释放,不然内存会被逐渐耗尽

delete [] cp;

该语句回收了 cp 所指向的数组。把相应的内存返还给自由存储区。

理论上,如果少了 [] 应该会导致少释放空间,从而产生内存泄露,因此,释放动态数组时,不能忘记 []

3.6、动态数组的使用

长度不一样的两个字符串,赋给同一个新的字符数组

const char *txt1 = "HAHAHA";
const char *txt2 = "HEHEHEHEHEHE";

const char *txt;
if(condition)    //condition表示某个条件
    txt = txt1;
else
    txt = txt2;

size_t demension = strlen(txt) + 1;
char *msg = new char[demension];
strncpy(msg, txt, demension);
cout << msg << endl;

4、新旧代码的兼容

4.1、混合使用string库和C风格字符串

可用字符串字面值初始化string对象

string str("Hello World");

通常,C风格字符串与字符串字面值有相同的数据类型,而且都是以空字符null结束,因此可以把C风格字符串用在任何可以使用字符串字面值的地方:

  • C风格字符串对string对象进行赋值或初始化;
  • C风格字符串可以作为string类型的加法操作两个参数中的一个;

但反之不成立,但可以通过名为 c_str() 的函数转化为C风格字符串

char *str1 = str;    //error
const char *str2 = str.c_str();    //ok

4.2、使用数组初始化vector对象

const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(int_arr, int_arr + arr_size);

//相当于用 int_arr[1], int_arr[2], int_arr[3] 初始化 ivec1
vector<int> ivec1(int_arr + 1, int_arr + 4);

举例1

编写程序: 从标准输入设备读入字符串,并把字符串存放在字符数组中(输入的字符串长度不定)。

int main()
{
    size_t arry_size = 10;
    char *p_str = new char[arry_size];
    int count = 0;//统计输入的字符数
    char c;//存放字符

    cout << "Enter some chars:" << endl;
    while (cin.get(c))//每次读取一个字符赋给c
    {
        if (count + 1 >= arry_size)//超出预设长度
        {
            arry_size += 10;//长度增加10
            char *p_temp = new char[arry_size];//申请新空间
            strncpy(p_temp, p_str, count);//将就空间的内容拷贝到新空间
            delete[] p_str;//销毁以前的p_str(p_temp)
            p_str = p_temp;//将新空间p_temp赋给p_str
        }

        p_str[count] = c;
        count++;
    }
    p_str[count] = '\0';//添加null结束符

    cout << "outputs:" <<endl;
    for (size_t i = 0;i != strlen(p_str); ++i)
    {
        cout << p_str[i];
    }
    cout << endl;
    cout << p_str << endl;
    delete[] p_str;//最后销毁p_str
    cout << "end" <<endl;

    return 0;
}

运行测试

Enter some chars:
asdfghjklqwertyuiop↙
^Z↙
outputs:
asdfghjklqwertyuiop(输出换行符)

asdfghjklqwertyuiop(输出换行符)

end

举例2

编写程序:读入一组string类型数据,将它们存储在vector中,然后将vector中的对象复制给一个字符指针数组。即为vector中的每个元素创建一个新的字符数组,然后把vector元素的数据复制到相应的字符数组中,最后将指针插入到指针数组中。

int main()
{
    vector<string> svec;
    string str;
    while (cin >> str) {
        svec.push_back(str);
    }

    char **cp_arr = new char*[svec.size()];//指针数组
    int cnt = 0;
    for (vector<string>::iterator iter = svec.begin(); iter != svec.end(); ++iter) {
        string s = *iter;
        char *cp = new char[s.size()+1];//+1表示为null结束符预留空间
        strncpy(cp, s.c_str(), s.size()+1);
        cp_arr[cnt] = cp;
        ++cnt;
    }

    for (int i = 0; i != cnt; ++i) {
        cout << cp_arr[i] << endl;
        delete[] cp_arr[i];
    }
    delete[] cp_arr;

    return 0;
}

运行结果

hello↙
world↙
^Z↙
hello
world

5、多维数组

严格的讲,C++中没有多维数组,只有数组的数组

初始化

int ia[3][2] = {
    {0, 1},
    {2, 3},
    {4, 5}
};

且等价于

int ia[3][2] = {0, 1, 2, 3, 4, 5};

而下面情况只初始化第一个元素,则其余初始化为0

int ia[3][2] = {{1}, {2}, {3}};

等价于

int ia[3][2] = {{1, 0}, {2, 0}, {3, 0}};

但不会等价于

int ia[3][2] = {1, 2, 3};//等价于int ia[3][2] = {1, 2, 3, 0, 0, 0}

下标引用

const size_t rsz = 3;
const size_t csz = 2;
int ia[rsz][csz];
for (int i = 0; i != rsz; ++i) {
    for (int j = 0;j != csz; ++j) {
        ia[i][j] = i + j;
    }
}

访问特定元素时,必须提供行下标与列下标

指针与多维数组

与普通数组一样,多为数组名也是指向第一个元素的指针。

int main()
{
    int ia[3][2] = {
        { 0, 1 },
        { 2, 3 },
        { 4, 5 }
    };
    int (*ip)[2];    //ok: 一个指向包含2个int值的数组的指针
    ip = &ia[1];    //ok: ia[1] 包含2个int值
    cout << (*ip)[0] << "," << (*ip)[1] << endl;
    return 0;
}

运行结果

2,3

int (*ip)[2];//指向数组的指针;即ip是指向包含两个int值的数组的指针
int *ip[2];//指针的数组;即ip是包含两个指针的数组

typedef简化多维数组指针

指向 ia 的指针

typedef int int_arr[2];
int_arr *ip = ia;

int_arr 输出 ia 的元素

int ia[3][2] = {
    { 0, 1 },
    { 2, 3 },
    { 4, 5 }
};
typedef int int_arr[2];
for (int_arr *p = ia; p != ia + 3; ++p) {
    for (int *q = *p; q != *p + 2; ++q){
        cout << *q << endl;
    }
}

结果

0
1
2
3
4
5

END.


Github Pages同步更新: Humooo's Blog

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,219评论 0 4
  • 一、字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 s...
    JaiUnChat阅读 1,655评论 0 7
  • 李笑来常说,七年就是一辈子。 我生于电影的奇迹之年,现在马上快要23岁。 “这辈子”是我开始职业成长的第一辈子,也...
    翟几枝阅读 378评论 11 2
  • 在职场,有些人经常会说些让别人很难堪的话,同事之间还好,如果你经常在领导面前口不择言,经常性的说这几句话,很可能会...
    无顾有长风阅读 946评论 0 1