C语言程序设计(八)
文章目录
字符串
字符数组
- char word[] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’,‘!’};
- 这不是C语言的字符串,因为不能用字符串的方式做计算
word[0] | H |
---|---|
word[1] | e |
word[2] | l |
word[3] | l |
word[4] | o |
word[5] | ! |
字符串
- char word[] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’,‘!’,’\0’}; //这才是C语言的字符串
word[0] | H |
---|---|
word[1] | e |
word[2] | l |
word[3] | l |
word[4] | o |
word[5] | ! |
word[6] | \0 |
-
以
0
(整数0)结尾的一串字符0
或\0
是一样的,但是和0
不同
-
0标志字符串的结束 ,但它不是字符串的一部分
-
计算字符串长度的时候不包含这个0
-
字符串以数组的形式存在,以数组或指针的形式访问
-
更多的是以指针的形式
-
string.h
里有很多处理字符串的函数
字符串变量
- char *str = “Hello”; //定义一个指针指向字符数组
- char word[] = “Hello”; //字符数组
- char line[10] = “Hello”; //字节长度为10的字符数组; char长度是1字节
字符串是数组; 这些变量都是字符数组的变量
- “Hello”
- ″Hello″ 会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的
0
。 {‘H’, ‘e’, ‘l’, ‘l’, ‘o’,‘!’,’\0’};像这样的字符数组;所以还需加一. - 两个相邻的字符串常量会被自动连接起来
- 行末的
\
表示下一行还是这个字符串常量
字符串
- C语言的字符串是以字符数组的形态存在的
- 不能用运算符对字符串做运算
- 通过数组的方式可以遍历字符串
- 唯一特殊的地方是字符串
" "
(双引号)字面量可以用来初始化字符数组 - 以及标准库提供了一系列字符串函数
字符串常量
char* s = "Hello, world!";
- s 是一个指针,初始化为指向一个字符串常量
- 由于这个常量所在的地方,所以实际上s是
const char* s
,但是由于历史的原因,编译器接受不带const
的写法, - 但是试图对s所指的字符串做写入会导致严重的后果
- 由于这个常量所在的地方,所以实际上s是
- 如果需要修改字符串,应该用数组:
char s[] = "Hello, world!";
#include <stdio.h>
int main()
{
int i = 0;
char *s1 = "Hello World";
char *s2 = "Hello World";
char s3[] = "Hello World";
//其中指针s1和s2指向同一个空间,也就是值相同; 而s3[]的数组是重新开辟一个空间,然后把字符数组的值复制进入
printf("&i=%p\n", &i);
printf("s1=%p\n", s1);
printf("s2=%p\n", s2);
printf("s3=%p\n", s3);
for(i=0; i<(sizeof(s3)/sizeof(s3[0])); i++){
printf("%c", s3[i]);
}//遍历输出字符数组
printf("\n");
s3[0] = 'b'; //注意字符数组中的元素要用单引号,不能是双引号
printf("s3[0]=%c\n", s3[0]);
for(i=0; i<(sizeof(s3)/sizeof(s3[0])); i++){
printf("%c", s3[i]);
}//遍历输出字符数组
printf("\n");
return 0;
}
输出结果:
&i=000000000062FE0C
s1=0000000000404000
s2=0000000000404000
s3=000000000062FE00
Hello World
s3[0]=b
bello World
注意:
char *s1 = "Hello World";
s1[0] = 'b';
//会直接报错,因为s1是一个指针,初始化为指向一个字符串常量,而这个常量是不可改变的,但是如果用数组就可以用数组:char s1[] = "Hello, world!";而数组是一种特殊的指针, 它指向一个字符串数组的首地址,
字符用单引号, 字符串用双引号
用指针还是数组?
-
char *str = “Hello”;
-
char word[] = “Hello”;`
-
数组:这个字符串在这里
- 作为本地变量空间自动被回收
-
指针:这个字符串不知道在哪里
- 处理参数
- 动态分配空间
-
所以如果要构造一个字符串用数组; 如果要处理一个字符串用指针
char*是字符串?
- 字符串可以表达为
char*
的形式; - 但
char*
不一定是字符串- 本意是指向字符的指针,可能指向的是字符的数组(就像
int*
一样;int*
不一定是数组; 它可能指向单个int,也可能指向数组) - 只有它所指的字符数组有结尾的0,才能说它所指的是字符串。
比如:char* word = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’,‘!’,’\0’};
- 本意是指向字符的指针,可能指向的是字符的数组(就像
字符串赋值?
char *t = “title”;
char *s;
s = t;
- 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
字符串输入输出
char string[8];
scanf(“%s”, string);
printf(“%s”, string);
#include <stdio.h>
int main()
{
char word[8];//可以放8个char类型,超过的话,字符数组会越界
char word2[8];
while(1){
scanf("%s", word);
scanf("%s", word2);
printf("%s##%s##\n", word, word2);
}
return 0;
}
输出:
hello world
hello##world##
hello
world
hello##world##
helloworld
hello_world
helloworld##hello_world##
可以发现%s读入一个单词(到空格、tab或回车为止)
scanf
读入一个单词(到空格、tab或回车为止)scanf
是不安全的,因为不知道要读入的内容的长度
安全的输入
char string[8];
scanf(“%7s”, string);
- 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一; 最后一个字符要放0.
- 下一次scanf从哪里开始?根据个数来读内容而不是空格或回车符.
#include <stdio.h>
int main()
{
char word[8];//可以放8个char类型,超过的话,字符数组会越界
char word2[8];
while(1){
scanf("%7s", word);//可以放8个char类型,但只能限制输入7个字符,最后一个字符要放0
scanf("%7s", word2);
printf("%s##%s##\n", word, word2);
printf("\n");
}
return 0;
}
helloworld
hellowo##rld##
1234
123456
1234##123456##
12345678
1234567##8##
常见错误
char *string;
scanf(“%s”, string);
- 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
- 由于没有对string初始化为0,所以不一定每次运行都出错
空字符串
char buffer[100]=””;
- 这是一个空的字符串,
buffer[0] == ‘\0’
- 这是一个空的字符串,
char buffer[] = “”;
- 这个数组的长度只有1!
字符串数组
char **a
- a是一个指针,指向另一个指针,那个指针指向一个字符(串)
char a[][]
- a是一个二维数组,第二个维度的大小不知道,不能编译,所以二维数组必须要有第二个维度的大小;
char a[][10]
- a是一个二维数组,a[x]是一个char[10]; 是指每个a[]单元最多有10个字符元素
char *a[]
- a是一个一维数组,a[x]是一个char*; 是一个指针
#include <stdio.h>
int main()
{
char a[][10]={
"Hello",
"World",
"1234567890"
}; //类似于数组的元素集合 ,因为是集合而不是块,所以得用分号;
return 0;
}
这样编译会出错,因为"1234567890"超过了9个char长度的大小,char a[][10]
是指每一行都不超过9个字符元素,(因为C语言中是字符串后面是\0
结尾占据一个空间,好比顾头不顾腚)。
But:
#include <stdio.h>
int main()
{
char *a[]={
"Hello",
"World",
"1234567890"
}; //类似于数组的元素集合 ,因为是集合而不是块,所以得用分号;
return 0;
}
这样写就没问题, 他们的区别是:
附加:之前用switch-case写的月份程序可以用数组来写
用char month[][20]
#include <stdio.h>
int main()
{
char month[][20]= {
"null",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
};
int i;
while (1) {
scanf("%d",&i);
printf("%s\n",month[i]);
}
}
用char *month[]
#include <stdio.h>
int main()
{
char *month[] = {
"null",
"January",
"February",
'March',
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
};
int i;
printf("请输入月份:");
scanf("%d",&i);
printf("%s\n", month[i]);
}
程序参数
int main(int argc, char const *argv[])
//括号里的参数(一个整数 一个字符串数组); 前面的整数是说明字符串数组有多大argv[0]
是命令本身- 当使用Unix的符号链接时,反映符号链接的名字
#include <stdio.h>
int main(int argc, char const*argv[])
{
int i;
for(i=0; i<argc; i++){
printf("%d:%s\n",i, argv);
}//遍历输出数组
return 0;
}
- 得在Unix上使用;windows上不能直接运行出结果
单字符输入输出
putchar函数
int putchar(int c);
//虽然是传入一个int类型参数; 但是它int型能接收的只是一个字符char- 向标准输出写一个字符(每一次只写入一个字符) put char:输出字符
- 返回写了几个字符,
EOF(-1)
表示写失败
每一次只写一个字符的意思是,每次
getchar函数
int getchar(void);
//没有参数- 从标准输入读入一个字符 get char 读入字符
- 返回类型是int是为了返回
EOF(-1)
Windows—>Ctrl-Z
Unix—>Ctrl-D
#include <stdio.h>
int main(int argc, char const*argv[])
{
int ch;
while( (ch = getchar()) != EOF){
putchar(ch);
}
printf("EOF\n");
return 0;
}
输出:
1234
1234
12656
12656
ch
ch
EOF
EOF
^Z
EOF
在windows系统中Ctrl + Z
再加回车会返回EOF
; 在Unix上用Ctrl + D
其他的输出什么就返回什么.
标准库中的字符串函数
-
C的标准库中的函数
-
头文件:
string.h
-
使用这些函数时得输入
#include <string.h>
string.h
strlen
函数size_t strlen(const char *s);
//传入的字符串不会修改 (const)- 返回s的字符串长度(不包括结尾的0); 就是专门用来输出数组字符串的长度, 与
sizeof
有区别; (sizeof
是直接返回占用内存空间的字节长度大小; 而strlen
呢是直接返回字符串的个数) - strlen函数的作用是它会告诉你字符串的长度
#include <stdio.h>
#include <string.h>
int main(int argc, char const*argv[])
{
char line[] = "Hello";
printf("strlen=%lu\n",strlen(line));
printf("sizeof=%lu\n",sizeof(line));
return 0;
}
输出:
strlen=5
sizeof=6
之所以sizeof=6, 是因为字符串数组中含有/0
, 而这个不会被strlen函数记进去。
仿写strlen
函数
#include <stdio.h>
//#include <string.h>
size_t mylen(const char* s)
{
int idx = 0;
while (s[idx]!='\0'){
idx++;
}
return idx;
} //仿照strlen函数
int main(int argc, char const*argv[])
{
char line[] = "Hello";
// printf("strlen=%lu\n",strlen(line));
printf("sizeof=%lu\n",sizeof(line));
printf("mylen =%lu\n", mylen(line));
return 0;
}
输出:
sizeof=6
mylen =5
其中这自己定义的mylen函数与标准库里的strlen函数功能一样。
-
strcmp
函数-
int strcmp(const char *s1, const char *s2);
-
strcmp
函数的作用是:比较两个字符串,返回: -
0:s1==s2
-
1:s1>s2
-
-1:s1<s2
-
#include <stdio.h>
#include <string.h>
int main()
{
char s1[] = "abc";
char s2[] = "abc";
printf("%d\n", strcmp(s1, s2));//字符串相同,则返回0
printf("%d\n", 'a'-'b');
char s3[] = "abc";
char s4[] = "ABc";
printf("%d\n", strcmp(s3, s4)); //s3和s4不同, s3>s4
printf("%d\n", 'a'-'A');
return 0;
}
输出:
0
-1
1
32
仿写strcmp
函数
#include <stdio.h>
#include <string.h>
int mycmp(const char *a, const char *b)
{
int c;
while (*a == *b && *a != '\0'){
a++;
b++;
}
if (*a-*b > 0){
c = 1;
}else if(*a-*b <0){
c = -1;
}else{
c = 0;
}
return c;
}
int main(int argc, char const *argv[])
{
char s1[] = "abc";
char s2[] = "abc";
printf("%d\n", mycmp(s1, s2));
printf("%d\n", 'a'-'b');
}
输出:
0
-1
strcpy
函数-
char * strcpy(char *restrict dst, const char *restrict src);
-
把
src
的字符串拷贝到dst
//注意是将后面的参数拷贝到前面restrict
表明src
和dst
不重叠(only C99)
-
返回
dst
-
为了能链起代码来
-
复制一个字符串
-
仿写一个
strcpy
函数
-
char *dst = (char*)malloc(strlen(src)+1);//先动态申请内存空间, , 字符串的长度要+1, 因为要包括最后的'\0'
strcpy(dst, src);
举个栗子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char s1[] = "abc";
char *s2 = (char*) malloc(strlen(s1)+1);//注意分配的内存大小要加1
strcpy(s2, s1); //将s1拷贝到s2,
int i;
for(i=0; i<strlen(s2); i++){
printf("%c", s2[i]);
}
return 0;
}
输出:
abc
也可以这样, 直接相同大小空间直接替换过去
#include <stdio.h>
#include <string.h>
int main()
{
char s1[] = "abc";
char s2[] = "ABC";
strcpy(s2, s1); //将s1拷贝到s2,
// int i;
// for(i=0; i<strlen(s2); i++){
// printf("%c", s2[i]);
// }
//可以不必要用for遍历输出字符串数组
//直接%s就可以输出字符串数组
printf("%s\n", s2);
return 0;
}
输出:
abc
仿写strcpy
函数(只写部分代码)
//第一种:
char* mycpy(char* dst, const char* src)
{
int idx = 0;
while (src[idx]){
dst[idx] = src[idx];
idx++;
}
dst[idx] = '\0';
return 0;
}//用数组定义的mycpy函数
//第二种:
char* mycpy(char* dst, const char* src)
{
char* ret = dst;//先保留起始地址
while (dst++ = src++);
dst[idx] = '\0';
return ret;
}//用指针定义的mycpy函数
-
strcat
函数-
char * strcat(char *restrict s1, const char *restrict s2);
- 把s2拷贝到s1的后面,接成一个长的字符串 (也就是连接起来)
- 返回s1
- s1必须具有足够的空间
-
安全问题
strcpy
和strcat
都可能出现安全问题, 可能造成数组越界, 超出存储范围, 就会有可能占到别的数据存放的地方, 这样会导致其他数据不安全,容易丢失。- 如果目的地没有足够的空间?
- 所以建议是尽量少用或者不用
strcpy
和strcat
-
安全版本(多了个n; 即最多能拷贝过去n的范围大小)
-
char * strncpy(char *restrict dst, const char *restrict src, size_t n);
-
char * strncat(char *restrict s1, const char *restrict s2, size_t n);
-
int strncmp(const char *s1, const char *s2, size_t n);
//这里的n是只是让它比较到第n个
-
字符串中找字符
-
strchr
char * strchr(const char *s, int c);
//在s中,从左边寻找c
第一次出现的位置, 并返回指针char * strrchr(const char *s, int c);
//在s中,从右边寻找c
第一次出现的位置, 并返回指针- 它们都是寻找单个字符的
- 返回NULL表示没有找到
举个栗子: 一个数组字符串hello
,找l
的后半段
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "hello";
char *p = strchr(s, 'l'); //从左边第一个'l'读起 ,strchr返回一个指针,指向一个你要找的字符
printf("%s\n", p); //返回的指针赋给指针p, 从指针p所指的地址开始输出字符串 ,因此不需要用*p ,与之前的指针指向某个变量的值不一样, 这个指针相当于数组头指针, 它指向的是一连串的数组空间
return 0;
}
输出:
llo
怎样找第二个l
呢 ?
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "hello";
char *p = strchr(s, 'l'); //从左边第一个'l'读起 ,strchr返回一个指针,指向一个你要找的字符
//寻找第二个l开始
p = strchr(p+1, 'l');
printf("%s\n", p);
}
输出:
lo
也可以将找到的那部分拷贝出来
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char s[] = "hello";
char *p = strchr(s, 'l'); //从左边第一个'l'读起 ,strchr返回一个指针,指向一个你要找的字符
char *t = (char*)malloc(strlen(p)+1);
strcpy(t, p);//将p的字符串拷贝到t中
printf("%s\n", t);
free(t);
return 0;
}
输出:
llo
如果是要l
的前一段呢?
这里要用到一些小技巧
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char s[] = "hello";
char *p = strchr(s, 'l'); //从左边第一个'l'读起 ,strchr返回一个指针,指向一个你要找的字符
char c = *p;//先保存p所指的空间上存放的值
*p = '\0'; //再将p所指空间上的值改为结束时的'\0'
printf("%s\n", s);//这时输出就只有'l'的前部分
char *t = (char*)malloc(strlen(s)+1);
strcpy(t, s);//将p的字符串拷贝到t中
printf("%s\n", t);//这时输出'l' 前面的部分
free(t);
*p = c; //将p所指的那个空间的原来的值恢复回来
printf("%s\n", s);//这时就是原来的值了
return 0;
}
输出:
he
he
hello
附注: 要输出字符串数组得用%s
, (%c
输出不了,除非用数组遍历才可以,那就相对比较麻烦)
-
strstr
- 是字符串中寻找一个字符串的
char * strstr(const char *s1, const char *s2);
char * strcasestr(const char *s1, const char *s2);
- 返回NULL表示没有找到
- …省略说明…