目录
- 字符串
- C语言字符串及相关函数
- 定义
- 字符数组和字符串的区别
- sizeof()和strlen()的区别
- 动态开辟字符串
- 野指针
- 常用字符串函数
字符串
C语言字符串及相关函数
在C语言中,字符串是由字符数组表示的一系列字符序列。C语言提供了一些函数来处理字符串,使我们能够对字符串进行操作和处理。以下是一些常用的C语言字符串函数的介绍:
-
strlen():用于计算字符串的长度,即字符串中字符的个数。
-
strcpy():用于将一个字符串复制到另一个字符串中。
-
strcat():用于将一个字符串连接到另一个字符串的末尾。
-
strcmp():用于比较两个字符串是否相等。返回值为0表示相等,小于0表示第一个字符串小于第二个字符串,大于0表示第一个字符串大于第二个字符串。
-
strchr():用于在字符串中查找指定字符的第一次出现位置,并返回该位置的指针。
-
strstr():用于在字符串中查找指定子串的第一次出现位置,并返回该位置的指针。
-
sprintf():用于将格式化的数据写入字符串中,类似于printf()函数。
-
strtok():用于将字符串分割为多个子字符串,可以指定分隔符进行分割。
这些函数提供了基本的字符串操作功能,可以帮助我们在C语言中进行字符串的处理和操作。使用这些函数时需要注意字符串的长度,避免数组越界和内存溢出的问题。
除了这些基本的字符串函数,C语言还提供了更多的字符串处理函数,如字符串转换函数(atoi、atof、itoa等)、字符串格式化函数(printf、scanf等)以及字符串查找和替换函数(strspn、strcspn、strpbrk等)。熟练掌握这些字符串函数的使用可以提高字符串处理的效率和准确性。
总结而言,C语言的字符串函数提供了丰富的功能,使我们能够对字符串进行各种操作和处理。通过合理运用这些函数,我们可以方便地处理字符串的拷贝、连接、比较、查找等操作,从而实现更加复杂的字符串处理需求。
定义
C语言并不存在字符串这个数据类型,而是用字符数组来保存字符串,但字符数组和字符串是完全不同的两个概念。
/*字符数组赋初值*/
char cArr[] = {'I','L','O','V','E','C'};
/*字符串赋初值*/
char sArr[] = "ILOVEC";
也可以写成如下等价形式:
/*字符数组赋初值*/
char cArr[6] = {'I','L','O','V','E','C'};
/*字符串赋初值*/
char sArr[7] = "ILOVEC";
对于字符串 sArr,可以直接使用 printf 的 %s 打印其内容;而对字符数组,很显然使用 printf 的 %s 打印其内容是不合适的。
//输出字符串方式
printf("%s", sArr);
puts(sArr);
字符数组和字符串的区别
-
字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以
\0
作为串的结束。char str[] = "hello"; //字符串数组,可以修改数组内容 char *pchar = "hello"; //字符串指针变量,此时的"hello"为字符串常量,不能再通过指针来修改其内容
-
字符数组是由若干个数组元素组成,它可用来存放整个字符串。
-
字符串指针变量只是一个指向字符串首地址的指针变量,我们可以对指针变量进行赋值,确定其指向的地址空间==(指针指向的字符串为字符串常量,不能再通过指针来修改其内容)==;
而字符串数组在定义时便在内存中为其分配了空间,也就是说,我们不能随意的改变这个数组的地址。对字符串指针方式 char *ps="C Language"; 定义时可以写为: char *ps; ps="C Language"; ------------------------------------- 而对数组方式: static char st[]={"C Language"}; 只能对字符数组的各元素逐个赋值,不能写为: char st[20]; st={"C Language"}; //错误
-
差别体现在sizeof的值。用字符串数组定义的"helloword"占11个字节,是因为"helloword"加上结尾的"\0"一共十一个char型字符,每个char型字符占1个字节;而用字符串指针变量定义时,sizeof的值仅为4个字节,这是因为s2是一个指针,在32位系统中,地址占4个字节。
我们通过一个小程序来认识这个区别。 #include <stdio.h> #include <string.h> int main() { char c1[] ="helloworld"; char *c2 = "helloworld"; printf("sizeof(s1) : %d %d\n", sizeof(c1), sizeof(c2)); printf("strlen(s2) : %d %d\n", strlen(c1), strlen(c2)); return 0; } 这段程序运行的结果是: sizeof(s1): 11 4 strlen(s2): 10 10 "helloword"一共10个字符,所以strlen的值都为10;
sizeof()和strlen()的区别
char str[] = "hello"; //结尾有'\0'
sizeof(str); //6,计算的是整个空间的长度
strlen(str); //5,只计算有效字符,会忽略结尾的'\0'
char *p = "hello";
sizeof(p); //8,p是一个char *
strlen(p); //5
动态开辟字符串
常用函数,需要包含头文件#include <stdlib.h>
举例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p; //未赋初值,为野指针
p = (char *)malloc(1); //p有了具体的内存指向
*p = 'c';
free(p); //释放空间,防止内存泄漏
p = (char *)malloc(12); //开辟新空间
strcpy(p, "yanghaoqing"); //为新空间赋值
int len = strlen("112123123adfadfasdf");
realloc(p, len - 12 + 1); //扩容,调整之前分配的空间,起始地址并未变化
strcpy(p, "112123123adfadfasdf");
puts(p);
free(p); //释放空间
p = NULL; //防止为悬挂指针,防止野指针
return 0;
}
野指针
**悬挂指针:**指针的指向内存被删除导致
-
例子1:
-
退出if的范围之后,c变量不再存在,p指向的地址被操作系统回收了。
-
指针被悬挂了,后续如果再被使用就会有问题。
-
int *p = nullptr;if (p == nullptr){undefined int c = 100;p = &c;}
-
-
例子2:变量p指向被释放了,指针被悬挂了,后续如果再被使用就会有问题。
int *p = nullptr;{undefined int *q = new intp = q;delete q;}
**野指针:**指针变量未初始化
指向不可用内存区域的指针(非法内存,垃圾内存)。野指针不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为if语句能够判断。但是野指针是很危险的,if不能判断一个指针是正常指针还是野指针。
野指针的成因主要有3种:
- 指针变量没有被初始化。 任何指针(全局指针变量除外,全局指针变量为NULL)变量在刚被创建的时候不会自动成为NULL指针,它的缺省值是随机的。所以指针变量在创建的时候,要么设置为NULL,要么指向合法的内存。
- 指针p被free/delete之后,没有置为NULL(最好加一句p = NULL;),经常性的我们会以为p是个合法的指针。他们只是把指针指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的就是“垃圾”内存。所以我们应该在释放完之后,立即将指针置为NULL,防止出现乱指的情况
- 指针操作超越了变量的作用范围。不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。也就是说指向的对象的声明周期已经结束了,然而我们还使用该指针访问该对象。
常用字符串函数
可能需要包含头文件#include <string.h>
输出字符串
char *p = "yanghaoqing";
puts("请输入一个字符串:"); //输出字符串后,会自动输出一个回车符
puts(p);
printf("%s\n", p);
获取字符串
gets() 函数原型
include <stdio.h>
char *gets(char *str);
gets() 函数注意事项
-
可以直接输入带空格的字符串
-
gets() 函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间。缓冲区(Buffer)又称为缓存(Cache),是内存空间的一部分。有时候,从键盘输入的内容,或者将要输出到显示器上的内容,会暂时进入缓冲区,待时机成熟,再一股脑将缓冲区中的所有内容“倒出”,我们才能看到变量的值被刷新,或者屏幕产生变化。
-
因为本函数可以无限读取,易发生溢出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破坏一个或多个不相关变量的值。
-
关于使用 gets() 函数需要注意:使用 gets() 时,系统会将最后“敲”的换行符从缓冲区中取出来,然后丢弃,所以缓冲区中不会遗留换行符。这就意味着,如果前面使用过 gets(),而后面又要从键盘给字符变量赋值的话就不需要吸收回车清空缓冲区了,因为缓冲区的回车已经被 gets() 取出来扔掉了。
# include <stdio.h> int main(void) { char str[30]; char ch; printf("请输入字符串:"); gets(str); printf("%s\n", str); scanf("%c", &ch); printf("ch = %c\n", ch); return 0; }
输出结果是:
请输入字符串:i love you
i love you
Y
ch = Y我们看到,没有清空缓冲区照样可以输入’Y’,因为 gets() 已经将缓冲区中的回车取出来丢掉了。如果前面使用的不是 gets() 而是 scanf,那么通过键盘给 ch 赋值前就必须先使用 getchar() 清空缓冲区。
gets() 函数使用例程
char *p = "yanghaoqing"; //不能更改指向的字符串
char str[128] = "\0"; //字符数组初始化\0
scanf("%s", str);
gets(str);
对字符指针变量所指向的内存单元进行初始化也可以用 gets()
char str[30];
char *string = str; //一定要先将指针变量初始化
gets(string); //也可以写成gets(str);
printf("%s\n", string); //输出参数是已经定义好的“指针变量名”
strncpy()函数
表示把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被赋值后的dest。
char *strncpy(char *dest, const char *src, int n)
strcpy()函数
自己实现字符串拷贝函数 strcpy() 和 strncpy()函数
#include <stdio.h>
char *myStrcpy(char *des, char *src)
{
if (des == NULL || src == NULL)
return NULL;
char *bak = des;
while (*src != '\0')
*des++ = *src++;
*des = '\0';
return bak;
}
char *myStrncpy(char *des, char *src, int count)
{
if (des == NULL || src == NULL)
return NULL;
char *bak = des;
while (*src != '\0' && count-- > 0)
*des++ = *src++;
if (count > 0)
while (count-- > 0)
*des = '\0';
else
*des = '\0';
return bak;
}
int main()
{
char str[128] = "\0";
char *p = "1231asdfas";
myStrcpy(str, p);
puts(str);
myStrncpy(str, p, 5);
puts(str);
return 0;
}
strcat()函数 字符串拼接函数
把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest
原来末尾的“\0”)。要保证*dest
足够长,以容纳被复制进来的*src
。*src
中原有的字符不变。返回指向dest的指针。
char *strcat(char *dest, const char *src);
#include <stdio.h>
#include <string.h>
#include <assert.h>
//自己实现
char *myStrcat(char *des, char *src) //写法1
{
assert(des != NULL && src != NULL); //断言,主动报错
char *bak = des;
while (*des != '\0') //定位到结尾
des++;
while (*src != '\0') //开始拼接
*des++ = *src++;
*des = '\0';
return bak;
}
char *myStrcat2(char *des, char *src) //写法2
{
assert(des != NULL && src != NULL); //断言,主动报错
char *bak = des;
strcpy(des + strlen(des), src); //先偏移有效字符个数后再拷贝字符串
return bak;
}
int main()
{
char str[128] = "casdfkadkdf";
char *p = "handsome";
char *pNew;
pNew = strcat(str, p); //有返回值,返回拼接好字符串的地址
puts(str);
puts(pNew);
myStrcat(str, p);
puts(str);
return 0;
}
strcmp()函数
比较相同位置上字符的ASCll码值的大小。
若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数
int strcmp(const char *s1,const char *s2);
#include <stdio.h>
#include <string.h>
#include <assert.h>
//官方实现代码
int myStrcmp(const char *string1, const char *string2)
{
int ret = 0;
assert(string1);
assert(string2); //判断是否等于NULL
//用unsigned char* 强转因为我们相减的字符ASC码值为正数没有负数,也就是无符号数
while (!(ret = *(unsigned char *)string1 - *(unsigned char *)string2) && *string2)
{
string1++;
string2++;
}
if (ret < 0)
ret = -1;
else if (ret > 0)
ret = 1;
return ret;
}
int main()
{
char *p1 = "12akdadf";
char *p2 = "23sdkfajl";
int ret = strcmp(p1, p2);
printf("%d\n", ret);
return 0;
}
strncmp()函数
功能是把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。
int strncmp ( const char * str1, const char * str2, size_t n )
memset()函数
包含在头文件 # include <string.h>
- memset函数为初始化函数,可以将一段连续的内存初始化为某个值。但它是以字节为单位进行初始化的。
- 函数的功能是:将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
- memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset初始化完后,后面程序中再向该内存空间中存放需要的数据。
- memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化。一般的变量如 char、int、float、double 等类型的变量直接初始化即可,没有必要用 memset。如果用 memset 的话反而显得麻烦。
- 当然,数组也可以直接进行初始化,但 memset 是对较大的数组或结构体进行清零初始化的最快方法,因为它是直接对内存进行操作的。
- 虽然参数 c 要求是一个整数,但是整型和字符型是互通的。但是赋值为 ‘\0’ 和 0 是等价的,因为字符 ‘\0’ 在内存中就是 0。所以在 memset 中初始化为 0 也具有结束标志符 ‘\0’ 的作用,所以通常我们就写“0”。
- memset 函数的第三个参数 n 的值一般用 sizeof() 获取,这样比较专业。注意,如果是对指针变量所指向的内存单元进行清零初始化,那么一定要先对这个指针变量进行初始化,即一定要先让它指向某个有效的地址。而且用memset给指针变量如p所指向的内存单元进行初始化时,n 千万别写成 sizeof§,这是新手经常会犯的错误。因为 p 是指针变量,不管 p 指向什么类型的变量,sizeof§ 的值都是 4。
函数原型:
#include <string.h>
void *memset(void *s, int c, unsigned long n);
void memset(首地址, 值, sizeof(地址总大小));
比如对数组a赋值:
memset(a, 0, sizeof(a));
memset以字节为单位进行初始化,这句话是什么意思呢?
就是说它进行初始化时并不关心你要初始化的数组是什么类型的,它均以字节为单位进行初始化。
比如你的数组是int型的。int为4字节。
举例1:
memset(a, 1, sizeof(a));
在a中每个元素占 4字节
比如int型的0为 0x00000000
int 型的最大值为 0x7FFFFFFF
0xF = 0x1111 占四位
1字节为8bit ,所以两位为1字节
使用memset进行初始化后会变为 0x01010101 也就是16843009
所以以下两种初始化效果是一样的
memset(a,-1,sizeof(a));
memset(a,255,sizeof(a));
举例2:
# include <stdio.h>
# include <string.h>
int main(void)
{
int i; //循环变量
char str[10];
char *p = str;
memset(str, 0, sizeof(str)); //只能写sizeof(str), 不能写sizeof(p)
for (i=0; i<10; ++i)
{
printf("%d\x20", str[i]);
}
printf("\n");
return 0;
}
根据memset函数的不同,输出结果也不同,分为以下几种情况:
memset(p, 0, sizeof(p));
//地址的大小都是4字节
0 0 0 0 -52 -52 -52 -52 -52 -52
memset(p, 0, sizeof(*p));
//*p表示的是一个字符变量, 只有一字节
0 -52 -52 -52 -52 -52 -52 -52 -52 -52
memset(p, 0, sizeof(str));
0 0 0 0 0 0 0 0 0 0
memset(str, 0, sizeof(str));
0 0 0 0 0 0 0 0 0 0
memset(p, 0, 10);
//直接写10也行, 但不专业
0 0 0 0 0 0 0 0 0 0