字符串详解
目录
定义
C 语言没有单独的字符串类型,字符串被当作字符数组,即char
类型的数组
- char str[20]="helloworld";
- char str[20]={"hello"};
- char str[20]={'h','e','l','l','o'};
双引号是代表是一个字符串常量,单引号代表是一个字符常量。
编译器会给数组分配一段连续内存,所有字符储存在相邻的内存单元之中。在字符串结尾,C 语言会自动添加一个全是二进制0
的字节,写作'\0'
字符,表示字符串结束。字符'\0'
不同于字符0
,前者的 ASCII 码是0(二进制形式00000000
),后者的 ASCII 码是48(二进制形式00110000
)。
在定义时若是担心数组大小不够 可以用下面这种写法:
char s[]="hello,acm";
编译器会自动给大小 ,这个大小是刚好合适的 是10.
ASCII 码对照表
输入输出
输入输出字符的时候 上面俩种写法是相同的
但是输入字符串的时候 scanf 和 gets 是有区别的
gets 的输入分隔符 只有回车,所以 gets 是能够读入空格的, gets 是可以读取到 空的回车的
scanf 输入分隔符 可以是回车也可以是 空格,scanf 是不能读取到 空的回车的。
puts 在输出字符串的时候 会给你自动加 回车符号
printf 不会自动加回车
思考一:
如果我输入 回车 回车 123 456 答案会是什么
思考二:
这样写算是对的吗? 如果是对的,那你觉得会输出什么呢
思考三
如果我想输入 123 和 'l' 的值 上面的这串代码能输入正常吗 ,如果不能,为什么。
字符串的转义字符
比较常用的转义字符:
- \n 回车
- \0 结束符
- \\ 输出一个\
- \0 开头 或者 \接1-3位数字 代表是8进制
- \x 开头 是十六进制数字 没有位数要求
思考四
下面的输出结果是什么 ,为什么
常见的字符串函数
strcpy 拷贝数组
用法:
strlen 输出字符串长度
和sizeof不同的是 strlen 是输出字符串长度 ,以 \0 作为结束符,sizeof 纯粹的是计算 占用的空间大小。char 类型 字符占用一个字节。
strcat 连接俩个字符串
strcmp 比较俩个字符串的大小
按照ASCII码来比较的。如果相等会返回 数字 0 ,因为编译器的不同 ,有些编译器小于的结果就是 -1 大于的结果就是 1 ,另外一些编译器返回的是 出现第一个不相同的字符的ASCII码的 相减的结果。
strupr 把字符串里面的小写转换成大写形式
strlwr 把字符串里面的大写转换成小写形式
字符串数组
如果一个数组的每个成员都是一个字符串,则需要通过二维的字符数组实现,每个字符串本身就是一个字符数组,多个字符串再组成一个数组。
定义
char str[10][20]={"hello","acm","!"};
只能省略 第一个中括号的数字 ,第二个不能省略。省略第二个参数会给你自动加,就像字符串会自动计算长度。
使用,正常和整形一维数组一样使用
进阶 指针
指针的简介
指针是什么?首先,它是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。
当定义变量 a 时 ,在内存重开辟了一段空间给 a ,这段空间的名字就叫 a ,在输入的时候,我们需要加地址符,是为了找到当前地址 来完成赋值输入。
&
运算符用来取出一个变量所在的内存地址
指针 其实就是地址 ,假如 a 是房子,那么 &a 就是a的地址, p代表的就是地址,所以要把&a赋值给 p 。
*
这个符号除了表示指针以外,还可以作为运算符,用来取出指针变量所指向的内存地址里面的值。
这里可以看成,就是说 p 是地址,*p 就是p当前地址的钥匙,带 *就是改地址带了钥匙,就能取出当前“房子”的具体的值。
我们定义是 带 * 是因为为了区别 这是一个指针变量。
了解上面之后
我们就不难了解下面这段代码
上面是代表这 一个 指针p指向了a的地址,因为输入的时候是需要地址的,p本身就代表a的地址,所以不用加 地址符,而第二种就是代表 需要加地址符来输入。因为 a 只是一个名字。
数组是如何存储的呢?
就是一段连续的空间 来存储,不一样的是,此时数组名字其实就是 该段连续空间的首地址
这也就是 为什么输入字符串的时候,不用加地址符号的原因,因为它本身就是地址了。
所以指针可以直接指向数组名所代表的地址。
指针的运算
指针也是可以进行自增自减运算的,当年你定义的不同类型的指针,它们才能代表不同的类型地址,有人可能会问,都是内存的地址,为什么指针还有类型区分?
我们知道,不同类型的数据,字节大小是不一样的,比如4个字节是 int 类型的数据,当做指针的运算时,指针到底往后面移动多少位置,才能获取到一个正确的数字?所以指针有类型,就是为了告诉我们这个指针做 运算的时候 更方便。
指针可以做 ++ 运算 也可以做 -- 运算
加入 p 在 0x004 的位置,p--就到了值为45的位置,++就到了 13 的位置。
思考五:
下面输出会是什么呢,为什么?
定义字符串还可以这样定义:
char *s="1234";
上面代表的是一个指针,指向字符串常量“1234” 因为字符串就是字符数组,所以可以这样指向。
下面这样定义的也是对的
我们在使用字符串变量的时候 比如 说这个
char a[20]="1234" 这个 a 是一个地址,但是这个a 不能修改地址,上面的 s 是可以的,因为 a 底层的 定义是 char* const a , 被const 修饰的值 是不能修改的,const修饰变量使其成为具有常属性变量。修改的时候是会报错的。 这也就是为什么不能修改 代表数组的 a 却可以修改指向,a的p指针
扩展:const修饰的指针
第二种写法其实实现的就是数组的功能,数组首地址不能修改,里面的值是可以修改。
指针一定要有指向才能使用,否则就是一个野指针。
一些等值的写法
指针使用:
常见误区:
上面这段代码 因为指针并没有 指向,然后就被 强制要往里面塞值,可是指针本身是不能塞值的,只能依附于有内存空间的去操作,所以这里,编译不会出错,这个 s 是无值的
思考六:
下面这段代码会输出什么呢,为什么?
复杂指针
学习完一级指针,就可以来学二级指针,下面分别代表的是 二级指针,指针数组,行指针。
二级指针
指的是 指向指针的指针
指针也有地址,不然他是如果保存地址的值的?,所以就出现了指向指针的指针。
就好像,有一排房屋(看成连续存储的数组),然后只需要一个指示牌A(看成指针)里面的值是第一个房屋的地址。我们就可以通过这个指示牌去找到第一个房屋的地址。找到这个社区
现在这个指示牌A的作用需要是每天存放 优秀社区 的称号,那我们想知道今天这个优秀社区房屋的地址,我们只认这个指示牌A上面的内容,社区内的人都知道这个指示牌A的位置,可是社区外的人并不知道,那么就可以来一个存储指向牌位置的指向牌B(二级指针),这个指向牌是保存了A的位置,然后外面的人就可以通过B来找到A,通过A找到 优秀社区。
思考七:
下面输出是什么,你能说出为什么吗?
指针数组:
整形有数组,字符有字符串,所以指针也有。
指针数组指的是 ,定义了一个数组,这个数组里面全是一级指针变量。
假如我定义了一个 *p[4] , p[0]代表的就是一个指针,这个指针可以指向一维数组的首地址,或者一个变量的地址。
行指针
指的是,定义了指向 二维数组行的指针,每次++都走列的个数 那么多。
行指针的形式,是 (*p)[4]
相信小伙伴们看到 (*p)[4] 和 *p[4] 脑袋都大了 我来说说我是如何区分的—— 在符号里面。是有优先级的,就好像 乘除 比 加减 运算要高,在这里 [] 比 * 的优先级要高
所以 *p[4] 先看的是 [] 那么他是一个数组 ,是一个 *p 类型的数组
而 (*p)[4] 给 *p 加了括号,先取的是 *p 后面是指定 运算 走的长度是 4,p++ ,每次都走4,所以更适合放在 二维数组里面。
一些等值的写法
int main()
{
char a[4][100]={"welcome to acm","welcome to hope","pyz is beautiful","lxh is stupid"};
char *p,**q,(*r)[100],*k[4];
r=a;
p=a[0];
q=&p;
k[0]=a[0];
puts(a[0]);
puts(k[0]);
puts(p);
puts(*q);
puts(*r);
k[1]=a[1];
puts(a[1]);
puts(k[1]);
puts(p+100);
puts(*q+100);
puts(*(r+1));
putchar(a[0][0]);
putchar(**q);
putchar(*p);
putchar(*(k[0]));
putchar(**r);
putchar(a[1][2]);
putchar(*(*q+102));
putchar(*(p+102));
putchar(*(*(r+1)+2));
putchar((*(r+1))[2]);
putchar(k[1][2]);
}