当前课程知识点:基于Linux的C++ >  第七讲 指针与引用 >  7.6 字符串 >  LinuxCPP0706

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0706在线视频

LinuxCPP0706

下一节:LinuxCPP0707

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0706课程教案、知识点、字幕

接下来的这一节就是字符串

有三个主要的理解角度

一个是作为字符数组

一个是作为指向字符的一个指针

还有一个就作为抽象的字符串的整体

三个方式来理解都可以

要特别记住 作为一个字符数组

和作为一个字符指针呢

两者之间是有差别的

我们首先来看字符数组

你如果像一个普通的数组一样

来定义一系列的字符其实也可以

像普通数组定义一样

我定义char s[8]

元素类型是字符 8个元素 8个字符

字符数组实际存储布局的时候

像普通数组一样“CPP-Prog”

就按照这个方式来保存就行了

这个字符数组怎么访问呢

数组是不能够整体赋值的

你只能一个字符、一个字符地去处理它

写一个for循环

很不符合实际情况

我们用字符串来表达这个信息

很少单独操作它的某一个单一的字符

单一字符表达的信息是有限的

我们往往是作为一个句子来处理

所以在这种情况下

操作起来是很不方便的

不仅仅操作起来不方便

可能会出一个大问题

当你出现多个字符数组的时候

有的时候在内存里边是区分不出来的

你比如说我有一个字符数组“CPP-Prog”

还有一个数组是“Hello”

假设“Hello”是存在前面的

那你看到的就是“HelloCPP-Prog”

它的存储布局 它是紧挨着数组嘛

0号元、1号元、2号元就按照顺序往下放

这个数组放完了

如果后面还是一个数组

继续顺序往下放 结果你看

存储布局里面写的是“HelloCPP-Prog”

你如果只看存储布局的话

你知道这是一个字符串

还是两个字符串呢 你不知道

你可能认为是一个字符串

也可能认为两个字符串

可能第一个字符串叫“Hello”

第二个字符串叫“CPP-Prog”

也可能第一个字符串叫“HelloCPP”

都有可能 你区分不出来

这种方案是不好的

那么我们怎么能够区分字符串呢

在每一个字符串的结尾

添加一个特殊的字符串结束标志叫‘\0’

‘\0’这个字符就是ASCII码值为0的那个字符

所以你看到定义的时候 就char s[9]

我里面要存8个有效的字符

后面还要封装一个额外的‘\0’

所以总共是9个

“Hello”那个呢 就是6个字符

“Hello”5个,加上‘\0’6个

现在有了‘\0’

这两个字符串存在我们内存里面的时候

你看到的就是“Hello\0CPP-Prog\0”

你一下子就看到了

这两个字符串是区分开了的

因为我知道‘\0’一到 字符串就结尾了

那么知道这个串结束了

后面“CPP-Prog”,‘\0’结尾了

这个串就结束了

这是很典型的存储布局

通过‘\0’区分开

它的好处是什么呢

好处就是可以在程序运行的时候

通过测试这个字符是不是‘\0’

来决定字符串是不是结尾了

我们在操纵字符串的时候

不用再关心字符串实际元素个数

你就一个字符一个字符往后看吧

看到‘\0’它就结束了

真想知道这个字符串有多长很简单

从前往后数 数到‘\0’结束

我们通过指针运算 可以直接就获取

这个字符数组里面特定的字符

但是你要记住

如果按照这样一个方式

来去分配内存的话

每次要为‘\0’

分配一个额外的存储空间

我们看这样一个例子

字符数组怎么访问

我让你写一个函数 返回一个字符c

在字符串s中第一次出现的位置

写这样一个函数 传字符c 传字符数组

直接写char s[]

我不知道这个数组有多长 没关系

也不需要传这个数组的元素个数

因为‘\0’作为它的结尾标志的

在它内部就写一个for循环

if(s[i]==c),return i

如果我找到这个字符 s[i]这个字符

是我们所要的字符c

我就return它的下标

这个循环都做完了

我也没有找到这个下标

那我就将返回很特殊的一个常数

假设这个常数已经定义好了

它的值是0xFFFFFFFF,8个F

就用它来表示不存在索引

我们字符数组就按照这个方式去访问

那如果使用一个指针来操纵它

那我们看这个例子

现在是Character新schar *s

指向字符的一个指针来表达字符串

一个for循环 定义了临时指针量t

一开始t是初始化成s的

t被赋值为s

然后当*t!=’\0’的时候

这个目标字符不等于‘\0’

说明我们这个串没结尾

然后我们就if(*t==c)

就return

否则的话就t++ 然后就找下一个字符

不断地循环下去

我们返回的是什么

同学们要特别记住 返回的是t-s

这两个指针的减法

s是指向这个字符串的0号元

也就是它的首个字符

t一开始指向0号元

后来t++指向1号元

后来t+2指向2号元

最后它会指向我们的‘\0’

就是那个字符串的结束标志

所以两者一减

这个字符串的元素个数

很明确吧 我们t-s

现在呢 因为它中间就有可能停啊

它找到了嘛 找到的话就t-s

因为是连头不连尾或连尾不连头嘛

所以t-s正好就是字符c

第一次出现的位置下标 t-s

如果没找着 同样返回我们的不存在索引

这个就是我们的字符指针

有了指向字符的这个指针

我们就可以用一个抽象的方案

来表达字符串

在C中我们使用char *

来表达一个抽象的字符串

我们可以把它定义出来typedef char * STRING

一定义出来

那么它就是一个抽象的字符串了

因为我不知道它内部实现细节了

也不使用char *来访问它了嘛

那不就是抽象的了吗

char *被我们typedef出来 变成STRING

我们就按照这个方式去实现

所以我们前面那个FindCharFirst

查找字符的首次发生的这样一个函数

它的三个基本的声明格式

第一个你把它当成一个数组

第二个你把它当成一个指针 没问题

第三你把它当作一个抽象的STRING也没问题

这三个函数原型对我们来说

它性质是一样的

但是要特别地记得

字符数组和字符指针

事实上是不一样的

如果你定义一个字符数组char s[9]

一个字符、一个字符地对它进行初始化

别忘了初始化‘\0’

那么实际上它的内部存储布局就是这样

它会为这个数组s分配9个字节的存储空间

一个字符、一个字符地写进去 “CPP-Prog\0”

如果你定义的是个字符指针

比如说char * s

把它初始化成双引号“CPP-Prog”

那么s作为一个指针

它会分配一段存储空间 4个字节

里面保存字符串的基地址

而那个字符串基地址里面会分配9个字节

来存“CPP-Prog\0”

注意我们这里面

双引号字符串文字尾部的‘\0’

是编译器为我们自动添加的

注意 这两者存储布局是不一样的

用char * s定义的时候

它会多一个指针变量

当你按照指针的格式定义字符串的时候

这个指针是可以直接赋值的

你像char * s 分号之后

s赋值为“CPP-Prog” 这是对的

我可以把这个字符串的文字分配好内存

然后把基地址传给s

如果你是字符数组的格式来定义char s[9]

s赋值为“CPP-Prog”

这个就不对了

不可以将字符串的文字赋值给一个数组

s是数组 为啥

因为数组不能够整体赋值

所以这个地方是不可以去做的

原因就是当你定char * s的时候

s是分配一个指针的存储空间

你把字符串文字赋值给它的时候

事实上是把字符串文字的基地址

赋值给s 这是合法的

而char s[9]会分配9个字符的存储空间

然后“CPP-Prog”

它本身也要分配9个字符的存储空间

保存进去 然后把这个基地址赋值给s

赋值不了 s是个数组

它必须接受9个字符

而不是接受9个字符的基地址

它是不一样的

初学者容易犯糊涂的地方

这是两条语句

可以合并成一条:char *s = “CPP-Prog”

这个是对的

定义一个指针量s 然后把它初始化成

字符串文字的基地址 没有问题

这两条语句合成一条:char s[9] = “CPP-Prog”

也是对的 两条语句合在一条语句是对的

分开也是对的

这两条语句合在一起是对的

分开是错的 这一分开

就涉及到数组的整体赋值 它就不对了

所以你要合在一起写char s[9] = “CPP-Prog”

这没问题 它就意味着这是编译器的问题

C/C++的编译器认为

如果你写char s[9] = “CPP-Prog”

其实就意味着

你分配了9个字符的存储空间

然后把后边那个字符串 那个文字

一个接着一个写到数组里边去

可是如果你分开写呢

就意味着要为s分配一段空间

然后“CPP-Prog”也分配一段空间

然后想把这个“CPP-Prog”的基地址

写到s里面去 那就不对了

要仔细领会两者之间的差别

然后我们来看这样一个例子

我想写一个函数来返回一个特定的字符串

你比如说我可以写一个函数

我有一个TransformCharIntoString转换

把一个字符c转化成字符串来输出

正常情况下面我们必须这么写

使用malloc进行动态内存分配

分配2个字节的存储空间

因为一个字符和一个字符串

它是不一样的

哪怕那个字符串只包含一个

单一的有效字符它也是不一样的

因为C的这个字符串是‘\0’结尾的

所以你不能够把一个字符串类型

赋值给字符类型

不能把一个字符类型赋值给字符串类型

两者不是赋值兼容的

所以你要想返回 那么必须要使用到

我们动态内存分配里面要用到的malloc

你要分配2个字节的存储空间

分配出来的空间转换成STRNIG

然后把它初始化

或赋值给STRING类型变量_s

然后就可以设_s的0号元是字符c

1号元是‘\0’然后我们就返回_s

这个方案是对的

可是如果想按照这个方案来 它就不对

你转换char _s[2],_s[0] = c,_s[1]=’\0’,return _s

这个程序是不对的 很典型

对于所有的返回值为指针的函数来讲

任何时候你都不能返回它局部量的地址

这个函数一结束它的局部量就消失了

你返回它的地址能行吗 那肯定是不行的

一访问程序就崩溃了 特别注意这一条

接下来就是非常重要的两个地方

C或C++为我们提供了

字符串操纵的一系列的库函数

C提供了一个标准的字符串库

位于头文件“string.h”

C++也为我们提供了字符串的操作

两者的实现策略是不一样的

那个库的名字也叫“string.h”

C++的编译器后来为了保证你不搞混淆

干脆统一地规定

“.h”这头文件的后缀不要了

C的标准库前面加一个‘c’

C++标准库就叫“string”

所以你要想使用C的字符串库

你包含头文件“cstring”

想使用C++的字符串库

包含头文件“string”

两者是不一样的

常用的C的字符串函数

包括strcat(合并两个字符串)

strcpy(拷贝一个字符串),strcmp(比较两个字符串)

strlen(求一个字符串的串长)

strtok(解析串中所有的标记)

如果你写C的程序

我们就必须按照这个方式来

如果你写的是C++的程序

不建议你使用标准字符串库了

你应该使用C++里面的string类来代替

那个方式和STRING的实现是不完全相同的

string类的那个实现

会提供一些更优雅的方案

用起来是很方便的

但是很不幸

因为如果你要写操作系统类型程序的话

很多操作系统内部的代码

它提供的都是C的字符串

char *这种架构的

所以C的标准字符串处理

有的时候还离不开

你如果只写C++代码

其实就只使用C++里面的string类就够了

string类 它就定义在我们的

标准头文件“string”里面

使用它的时候简单

你就声明或者构造这样一个对象

string s定义这个对象

然后把它初始化成“abcdefg”

或者你就直接构造的时候就对它初始化

string s(“abcdefg”)

这个面向对象的架构

我们后面会讲 现在你就记住它

我们就按照这两个方式都可以

后一种方式会更好一些

string s就直接构造的时候

就对它初始化“abcdefg”

想读取和写入这个对象其实很简单

cout、cin 就string类

它因为已经重载了

“<<”和“>>”这两个操作符

想读取包含空格和制表符在内的

整行文本信息

可以使用这个函数getline

从cin这个输入流里面去读取

然后把这个东西读到s字符串里面去

最后第三个参数传递的是‘\n’

表示以‘\n’来结尾 我们读这一整行

按照这个格式去进行操作

你要想知道字符串对象长度

那么很典型 它提供了一个成员函数叫length

length首先是一个函数

因为它是从属于string类这个对象的

所以我们要访问的话

就像一个结构体一样

访问s.length() 调用这个函数

就能够知道这个string到底有多少个字符

它就会告诉你

想改变这个字符串对象容量大小

你就可以写s.resize(32)

那就把它的尺寸设成32个字符这个长

想把第二个字符串

追加到第一个字符串的尾部

s1假设被初始化成“abcd”

s2被初始化成“efg”

你就s1的后边

调用它的append成员函数追加

其实它还提供更简单的一个方案 加号

就把s1+s2赋值给s1 或者s1+=s2

提供了重载的操作符 你可以直接用

接下来是字符串比较操作

s1初始化成“abcdefg”

然后s2初始化成“abcdxyz”

我要比较这两个字符串大小关系

那么就调用s1.comepare(s2,0)

0 第二个参数表示它从0号位开始比较

你要特别记得的是

比较函数 相当于关系操作符

s1是它的左操作数 s2是它的右操作数

所以我们在s1上面去做comepare

s2本身也是string类的对象

所以s2实际上也可以做s2.comepare(s1,0)

但是那个时候s2就是它的左操作数

这两个比较关系 你要比较小于号

一个是比较s1

所以两者关系正好可能是反过来的

使用的时候要特别注意这一点

剩下的就是查找

你要想在这个串里面

查找指定的一个子串

比如s1是“abcdefg”

我要查找“bcd”这个子串

那么你查的时候s1.find(s2)

在s1这个子串里面找s2

0还是从0号位开始查找起

它将会返回s2

在s1中第一次出现的时候

它的那个位置

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0706笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。