字符串入门

基础

        c语言中没有为字符串定义专门的数据类型。字符串的字符存储在相邻的内存中,而数组是由相邻的存储单元组成,所以将字符串存储在字符数组中是很正常的事。

        一个字符串的终止字符必须是'\0',所以定义字符数组时,它的长度一定要比字符串中字符的个数多1,用于存储最后一个'\0'。如果没有加上'\0',在输出字符串时系统会沿着字符数组的地址一直读下去,直到遇到'\0'时停止,这就可能造成输出的字符串最后是一串乱码。

        如果字符串常量中间没有间隔或间隔是空格,那么系统会将它们串联起来。如下:

	char* str = "这是开始""这是结束" "这是空格";
	printf("%s\n",str);//输出值为"这是开始这是结束这是空格"

        字符串常量属于静态存储类,如果在一个函数中使用了某个字符串,即使是多次调用这个函数,该字符串在整个运行过程中只存储一份。

        整个引号中的内容做为指向该字符串存储位置的指针

	printf("%s,%p,%c\n","这是开始","这是开始",*"a是开始");//这是开始,0040A0C0,a
        对字符串使用*只能取得该字符串存储位置第一个字节中的数据,因此获得的是a。

        所有的字符串常量都是存储在静态存储区中,都属于静态存储类

定义

        1,使用#define定义字符串常量。如#define MSG "this is message"。

        2,使用字符数组。如char str[] = "this afda rqweraf432 fads";引号的结尾会自动加上结束标识符‘\0’。在指定数组大小时,必须确定数组元素个数至少比字符串长度多1(用于存储结束标识符),未被使用的元素都被自动初始化为'\0'

        3,使用字符指针。与使用字符数组类似,但两者依旧有些区别:

        数组定义时,会将字符串从静态存储区中复制一份,此后编译器会把数组名看作数组的首元素的地址,而数组名就是一个常量,不能进行修改。而使用指针时只是将字符串在静态存储区中的首地址复制出来,同时为变量名分配一个存储空间用来存储字符串的首地址,此时指针可以进行修改,使其指向别的存储空间。

        总之:数组初始化是从静态存储区中把一个字符数组复制出来,而指针初始化只是复制字符串的地址。因此,数组初始化的字符串可以进行修改(它只是修改了静态存储区中字符串的复本,而不是修改静态存储区中字符串的值),而指针初始化的字符串不能进行修改(字符串本身还存储在静态存储区中,所以不能进行修改)。如:

	char a[] = "this is a string";
	char* b = "this is a string";
	a[1] = 'a';//允许  修改了this is a string的复本
//	*(b+1) = 'b';//不允许 因为this is a string存储于静态存储区中,b只是存储了它的首地址
	printf("%s,%s\n",a,b);//tais is a string,this is a string
        并且a是一个常量,不能执行a++之类的操作,而b是一个变量,可以重新赋值为另一个字符串。因此,可以使用b = a;但不能使用a = b;(因为a是一个常量)。

        上面代码片段中的不允许可能会在某些编译器上通过,从而导致程序问题。因此,最好的做法是:使用指针声明字符串时,在前面加const关键字

        4,字符串数组。如:char* strarr[num],数组中存储的是字符串的地址。因此strarr[0]是一个地址,指向第一个字符串的地址。如果使用char strarr[m][n]形式,将数组本身也被复制到strarr中。除上述区别之外,第二种定义方式定义了一个所有长度都相同的数组(即strarr[m]的长度都是n),但第一种不会。而且第一种方式定义的字符串数组在内存中不必连续存放,但第二种却是连续的。

与数字互转

       数字转字符串:使用stdio.h中的sprintf()函数进行:例如:

    sprintf(s, "%d",10);//将整数10转为字符串
        字符串转数字:使用stdlib.h中atoi(alphanumeric to integer),atof(a to float),atol(a to long)等类似的方法。这些方法将字符串转换为相应类型的数字。

        如果字符串只是以数字开头,那么上述方法一样可以执行,它们会将从开头处能转换的字符全部转换成数字。如:

    char* s="12.4fdasf";
    char* s2 = "2.342aa";
    printf("%d,%.3f\n",atoi(s),atof(s2));//12,2.342
        其中第一个遇到小数点就停止了,因为整数不能包含小数点。第二个一直到字母aa时才停止转换。

        另外,stdlib.h中还提供了一系统strto**方法,如strtol(string to long),strtoul(string to unsigned long),strtod(string to double)等方法。相较于上面的atoi等方法,这些方法的复杂性在于它们可以报告字符串中第一个转换失败时字符的地址。另外,转换为整数时还可以指定进制——strtol,strtoul等方法中第三个参数的作用。如下:

    char* c;
    char* s1 = "10";
    printf("%ld,%d\n",strtol(s1, &c, 10),*c);//10,0   代码一
    printf("%ld,%d\n",strtol(s1, &c, 16),*c);//16,0   代码二
    char* s2 = "10aikl";
    printf("%ld,%d\n",strtol(s2, &c, 10),*c);//10,97   代码三
    printf("%ld,%d\n",strtol(s2, &c, 16),*c);//266,105  代码四
        代码一中,可以将10完全转换成整数,因为c中存储的就是空字符的地址,其ASCII码值为0,所以%d输出结果为0。

        代码二中,*c的输出原因同上。但由于第三个参数指定的是16,在16进制中10表示的是十进制中的16,所以第一个输出的是16,而不是10。

        代码三中,97是字符a的ASCII码值,因为在10进制情况下,a无法转换为整数,因此会在字符a处转换失败,所以c中存储的就是s2中字符a的地址。以%d形式输出*c时输出的就是97(字母a的ASCII码值)。

        代码四中,以十六进制形式转换,字符a可以转换成数字,所以c中存储的应该是t的地址,以%d形式输出时为105(t的ASCII码值)。

读入与写出

        读入之前,必须首先留出足够存储字符串的空间。最简单的方法是用数组形式,如char a[n],进行声明,那么在声明的时候就会为a分配了一个n个字节的存储空间。或者在声明指针的时候,使用malloc()分配指定大小的空间

        字符串的读取使用stdio.h中的gets(),fgets()或者scanf(“%s”)进行。写出使用相对应的puts,fputs()以及printf()。

换行

        如果字符串够长,可以使用\+[回车键]进行换行。就这使得屏幕上的文本另起一行,在显示的时候不会包括换行和\字符。不过,下一行代码从最左边开始起,并且不需要单独使用双引号。如

	char* str = "reqrasf522345fadsf99\
fdsafasdf";
	printf("%s",str);

string.h

        该头文件中定义许多与字符串操作相关的函数的原型。如下:

        strlen:获取某个字符串的字符个数,不包括最后一个终止字符。

        strcat:它将第二个字符串的一份复本添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,从而改变了第一个字符串的值,但第二个并没有改变。该函数的返回值为第一个参数的值。如下:

	//url,reed都char*
	char* result = malloc(strlen(url)+strlen(reed)+1);
	strcpy(result,url);
	strcat(result,reed);
        strcat并不会检查第一个数组是否能够容纳第二个字符串。如果没有为第一个数组分配足够的存储空间,多出来的字符将溢出到相邻的存储单元,这就有可能会导致一些问题。
        strncat():大致与strcat()相同,但需要另外一个参数指明最多允许添加的字符的数目,直到加到最大的字符个数或者遇到空字符为止,两者先符合的那一个来终止添加过程。如:
	char c[10] = "abc";
	char* d = "abcabcabcabcabcabc";
	strncat(c,d,10-strlen(c)-1);//10-strlen(c)-1用于计算c还可以追加多少个字符

        strcmp()比较两个字符串是否一样,而不是比较两个字符串的地址是否一样。无论通过字符数组还是字符指针定义字符串,使用关系运算符进行比较时,都是比较相应变量中的值——即字符串的地址,而strcmp()比较的是字符串的内容。

        strcmp()会依次对参数中的所有字符进行比较,包括结束字符,直到出现两个字符串都结束或者出现不一样为止。 strcmp()是按机器编码顺序进行比较,这意味着字符的比较是根据它的数字表示法,一般是ASCII值。

        如果第一个字符串在字母表中顺序的先于第二个,则返回负数;相反返回的就是正数;如果两个相等返回的就是0。可以简单理解为返回两个字符串的差值的符号。

        strncmp():与strcmp()类似,但strcmp()会一直比较到找到不同的字符为止。而strncmp()可以由第三个参数指定要比较的字符个数。

        strcpy():复制字符串内容,并且复制结尾字符。使用字符数组或字符指针变量为别的变量赋值时,只是复制字符串的地址,而并没有复制字符串,strcpy()就是复制字符串本身。第一个参数称为目标字符串,第二个参数称为源字符串,strcpy是将源字符串复制到目标字符串。

        它返回值为char*类型,即第一个参数的值。另外第一个参数不需要指向字符串的开始位置,这样就可以把源字符串复制到目标字符串中指定的位置。

        strncpy():strcpy()与gets()一样,不检查目标字符串能否容纳下源字符串。而strncpy()需要使用第三个参数指定最大的复制字符个数。strncpy(target,source,n)从source中把n个字符(或空字符之前的字符)复制到target中。因此,如果源字符串比n小,则源字符串整个都被复制过来,包括结束字符;如果源字符串比n大,就不会复制空字符。

        sprintf():为stdio.h中的方法,用于组合字符串。第一个参数为目标字符串的地址,其余的参数和printf()一样:一个转换说明字符串,接着旧要写的项目的列表。如sprintf("a","%s,%d","b",100);此时类似于Java中的"a"+"b"+100。如下:

    char s[81];
    sprintf(s, "%s+%s+%d","start","end",10);
    puts(s);
        由于第一个参数的内容将会变化,因此不能将第一个参数传递成"str"形式,或者是不可变的char*形式。
        strchr(char*s,int c):返回s中字符c第一次出现的位置的指针,如果没有字符c返回空指针(由于空字符也是字符串的一部分,因此也可以用来查找空字符)。如下:
    printf("%p,%p\n","abcdefg",strchr("abcdefg", 'c'));
    //输出值为:0x100000f8f,0x100000f91
    printf("%ld",strchr("abcdefg", '\0')-"abcdefg");//7,即strlen("abcdefg")

        输出的第一个值为8f,即字符串在内存中的首地址,而c为字符串中第3个数,char又占一个字节,因此strchr的返回结果为91。

        strrchr(char*s,int c):与strchr()类似,返回s中c字符最后出现的位置的指针。类似于Java中的Striing#indexOf()与String#lastIndexOf()函数。

        strstr(char* s1,char* s2):指向s1中第一次出现s2的地方。如果s1中不包含s2,就返回空指针。

        strpbrk(char*s1,char* s2):指向字符串s1中存放字符串s2中的任何字符的第一个位置,如果没找到任何一个字符,就返回空指针。如下:

    printf("%p,%p,%p","abfdasfew",strpbrk("abfdasfew", "1f2qer"),strpbrk("abfdasfew", "1ef2qr"));
    //0x100000f85,0x100000f87,0x100000f87

        后两个返回值是一样的,因为字符f在字符串s1中出现的位置比字符e靠前,所以应该返回f的位置,而不是e的位置。

        memcpy()与memmove():strcpy()与strncpy()只能对字符数组进行复制,而这两个函数可以对任意类型的数组进行复制。两者都是由第三个参数指定的位置复制n(第三个参数)个字节到第一个参数指定的位置处。两者的区别为:第一个方法是假定两块内存区域没有重叠的部分(因为参数都用restrict修饰了),但第二个方法却没有这个限制。在复制过程中,两个方法不关心数据类型,只是直接将一些字节从一个位置复制到另一个位置。因此,可以将结构体数组复制到int数组中,只不过在使用时将字节中的数据按int进行解释,得到的结果很奇葩罢了。如下:

void test() {
	int a[10] ={2,34,5,65,4,3,6,8,7,1};
	int b[10] ={20,52,0,50,5,87,56,2,5,56};
	double c[5] = {3.0,4.6,6.09,7.094,8.1};
	memcpy(a,c,5*sizeof(double));
//	memmove(a,c,5*sizeof(double));  与上一个语句输出相同
	showarray(a,10);
}
        输出的结果根本不会是c数组中的值。因为显示时将double分解成int后。

练习

        strstr函数的实现:

char* string_in( char* dst, char* src){
    char* d_save = dst;
    char* s_save = src;
    long len = strlen(dst);
    int index;
    if(*dst == '\0' || *src=='\0'|| strlen(src)>len){
        return NULL;
    }
    for(index=0;index<=len-strlen(src);index++){
        const int temp = index;
        if(dst[index] == src[0]){
            dst+=temp;
            while (*src) {
                if(*src != *dst){
                    break;
                }
                src++;
                dst++;
            }
            if(*src == '\0'){//遍历src中的所有字符
                return d_save+temp;
            }
            dst = d_save;//将dst,src复位
            src = s_save;
        }
    }
    return NULL;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值