数组与字符串操作函数

数组:一组具有相同数据类型的数据的集合。
         1) 一维数组
         2) 二维数组
         3) 字符数组

1、一维数组

   定义格式:
        类型说明符  数组名[整数表达式];
        “类型说明符”:指定数组元素的类型,任意C语言合法的类型都可以。
        “数组名”:遵循C语言标识符命名规则,是一个常量,其值为首元素的地址。
        “整数表达式”(整型常量、符号常量、常量表达式):指定数组元素的个数。现在也可以是变量、变量表达式,并且变量、变量表达式必须有明确的值。如果是变量、变量表达式则数组不能初始化

    eg:
        int a[10];  //定义了一个有10个元素的数组 a,每一个元素都是 int 型。

    一维数组在内存中存放:
        在连续的地址空间,从低地址到高地址依次存放数组中每一个元素。

    一维数组元素的引用:(只能引用数组元素而不能一次整体调用整个数组全部元素的值)
        数组名[下标]
        “下标”:既可以是整型常量、符号常量、常量表达式也可以是变量、变量表达式,但必须是>=0的整数
            在C语言中,下标是从0开始的。最大值是  数组大小-1

            a[0]、a[1]、...a[n-1]
        引用数组元素 a[i] 跟普通变量一样,即可以作为左值也可以作为右值,还可以取地址。

    一维数组的初始化:
        数组的初始化用 { } ,并且必须在定义时直接初始化。
        (1)全部初始化
            int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
        (2)可以对部分元素初始化,后面的元素就自动初始化为0
            int a[10] = {1, 2, 3, 4, 5};
        (3)如果对全部数组元素赋初值,那么可以不指定数组长度(元素个数)
            int a[] = {1, 2, 3, 4, 5, 6};

        eg:
            int a[100] = {0}; // 一般用于数组元素清0

#include 
int main(int argc, char *argv[])
{
    int s[12] = {1, 2, 3, 4, 4, 3, 2, 1, 1, 1, 2, 3}, c[5] = {0}, i;
    for(i = 0; i < 12; i++)
        c[s[i]]++;
    for(i = 1; i < 5; i++)
        printf("%d ", c[i]);  //4 3 3 2
    printf("\n");
	return 0;
}

2、二维数组                                                                                                                 

      数组元素可以出现在表达式中,也可以被赋值。  例如: b[1][2]=a[2][3] / 2                                                                              
     int a[4];   //定义了一个数组a,有4个元素,每个元素都是int类型
     再定义一个数组 b        typeof(a) <==> int [4]
         typeof(a) b[3]; // 定义了一个数组b,有3个元素,每个元素都是 int[4]类型
     ==> int[4] b[3];
     ==> int b[3][4];
     由推导过程可以得出:
二维数组实际上就是一个一维数组,只不过该一维数组的元素又是
                                         一个一维数组罢了。

     我们再实际应用中,通过把 int b[3][4] 看成一个3行4列的矩阵。

     二维数组定义:
       
 类型说明符 数组名[整数表达式][整数表达式];
                                          多少行       每行多少列

     二维数组在内存中的存放:
       
 按行存放,即先顺序存放第一行的元素,再放第二行的元素...

     二维数组的引用:
     
   数组名[第几行][第几列]

     二维数组的初始化:用{}实现初始化,且在定义时直接初始化。
       
 (1)分行给二维数组赋初值
             int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
         (2)将所有数值写在一个花括号里,按数组的排序顺序对元素依次赋初值
             int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
         (3)只对部分元素赋初值,其余的元素自动置0
             int a[3][4] = {0};   等价于   int a[3][4] ={ };
             int a[3][4] = {1, 2, 3};
             int a[3][4] = {{1, 2, 3}, {5, 6}, {9, 10, 11, 12}};                                                                     
(4)如果对全部元素都赋初值,则定义数组时可以对第一维的长度省略。
             int a[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

3、字符数组(一次整体调用整个数组全部元素的值)及字符串操作函数     

1)C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符数组中的                2)用"%s"格式符输出字符串时,printf函数中的输出项是字符数组名,而不是数组元素名                        例:printf("%s", c);                                                                                              3) 如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定为空字符('\0')                                                    

4)  在遇到字符 '\0' 时,表示字符串结束,把它前面的字符组成一个字符串                              5)中文也是字符  

  字符数组,就是数组元素是 char(字符型)的数组,它可以一维的,也可以是二维的。    
        char c1[5] = {'c', 'h', 'i', 'n', 'a'};
        char c2[6] = {"china"}      等价
于   char c2[6]= "china";
        两者有什么区别?
        char c2[] = "china";
        <==> char c2[] = {'c', 'h', 'i', 'n', 'a', '\0'}; 
 //  多一个'\0'

    字符串在保存时会在末尾加一个终止符 '\0','\0'的ASCII为0,终止符的作用就是用
    来标识字符串的结束。

    我们在进行操作字符串都是带入字符串的首地址,正因为每个字符串都会有一个终止符,
    所有系统才会知道字符串的具体范围。

注意:

#include 
#include 

int main(int argc, char *argv[])
{
	char str[3][3] = {
		{'1', '2', '3'},
		{'1', '2', '3'},
		{'1', '2', '3'},
	};
	printf("%s\n", str[1]); //123123
	return 0;
}

⭐数组名代表一个数组的首地址(首个元素的地址)

正确:c h i n a
#include 
int main(int argc, char *argv[])
{
    char c[] = "china";
    char *p = c;
    while (*p) //while (*p != '\0')
        printf("%c ", *p++);
    return 0;
}

错误:p越界但不指向空
#include 
int main(int argc, char *argv[])
{
    char c[] = "china";
    char *p = c;
    while (p) 
        printf("%c\n", *p++);
    return 0;
}

字符串操作函数:                                                                                                                      

  (1)用getsputs一个字符串进行输入输出          gets[字符数组名];   puts[字符数组名];
            eg:
               char ch[10];
           
   gets(ch);  //在键盘上输入一个字符串,保存到ch指向的内存空间中
               puts(ch);  //把ch指向的空间中保存的字符串打印出来
        注意:
            a) gets有巨大的Bug,
gets这个函数不会考虑保存这个字符串的数组的空间大小,
                如果超过了分配范围还会继续写数据,可能会改变别的变量的数据.

                                           例: 

                                                    char c[3];

                                                    char b[3] = {'a','b','c'};

                                                    gets(c);  //sscc

                                                    puts(c);  //sscc

                                                    puts(b);  //c

         b) gets函数不会去获取 '\n',puts在输出时将字符串结束标志'\0'转换成'\n',即自动换行
         c) gets获取字符串时,可以获取空格、tab等空白字符,回车结束输入.
                   而
scanf 是遇到空格、tab、回车等空白字符就结束输入
        
    终端指令:man gets //查找man手册中 gets 函数的说明
    NAME
        gets - get a string from standard input (DEPRECATED)

    SYNOPSIS
       
#include

        char *gets(char *s);

 (2)  strcpy/strncpy----字符串拷贝函数              strcpy(字符数组1,字符串或字符数组2)                不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组,字符数组名是一个地址常量,不能改变其值。   
        NAME
            strcpy, strncpy - copy a string

        SYNOPSIS
           
#include

            strcpy:字符串拷贝函数.用来把src指向的字符串,拷贝到dest 指向的内存中去.
            char *strcpy(char *dest, const char *src);
                @dest:指向用来保存字符串的内存空间
                @src:指向要拷贝的字符串数据
            返回值:
                如果成功,返回拷贝后字符串的首地址, 即 dest
                如果失败,返回 NULL
        注意:
strcpy函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出.
        
       
strncpy正式为了修复strcpy的bug的,它的功能和strcpy相似,
        只不过他
顶多拷贝n个字节.
            char *strncpy(char *dest, const char *src, size_t n);
                @dest:指向用来保存字符串的内存空间
                @src:指向要拷贝的字符串数据
                @n:最多拷贝n个字节
            返回值:
                如果成功,返回拷贝后字符串的首地址, 即 dest
                如果失败,返回 NULL

        注意:strncpy到底拷贝了多少个字节呢?
       
    1) 遇到 '\0',拷贝结束('\0'有拷贝的)
            2) 没遇到'\0',但是已经拷了n个字节了,结束拷贝(没有拷贝'\0')

       如果在复制前未对字符数组1初始化或赋值,则字符数组1各字节中的内容是无法预知的,复制时将字符数组2中的字符串和其后的'\0'一起复制到字符数组1中,取代字符数组1中的前面几个字符,最后几个字符并不一定是'\0',而是字符数组1中的最后几个字节的内容                                  例:字符数组内存从小到大连续存储(节约内存)                                                            char ch1[ ] = {'a', 'b', 'c'};                                                                                                        char ch2[ ] = {'b', 'c', 'd'};  //存储 a b c b c d   

                 从小到大排序  a b c a a a b c

                char ch1[100] = {'a', 'b', 'c'};                     char ch1[100] = {'a', 'b', 'c'};

                char ch2[] = {'a', 'b', 'c', 'a', 'a'};               char ch2[10] = {'a', 'b', 'c', 'a', 'a'};

                puts(ch2);  //abcaaabc                              puts(ch2);  //abcaa  //原因:有'\0'

                strcpy(ch1, ch2);                                       strcpy(ch1, ch2);       

                printf("%s", ch1);   //abcaaabc                 printf("%s", ch1);  //abcaa

注意事项:

1. 遇到 '\0'拷贝结束('\0'有拷贝的)

2. arr2数组大小如果小于arr1数组大小:必须以 '\0'结束

#include 
#include 

//错误 数组arr1越界
int main(int argc, char *argv[])
{
    //字符数组内存从小到大连续存储(节约内存) abcd**********\0 
	char arr1[] = "**********"; //有'\0'结尾
	char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2));
 
	return 0;
}

//正确
int main(int argc, char *argv[])
{
    //字符数组内存从小到大连续存储(节约内存) abcd**********\0 
	char arr1[14] = "**********"; //10个‘*’  有'\0'结尾
	char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); //输出:abcd**********

	return 0;
}

//正确
int main(int argc, char *argv[])
{
    //字符数组内存从小到大连续存储(节约内存) abcd\0********** 
	char arr1[10] = "**********"; //有'\0'结尾
	char arr2[] = { 'a', 'b', 'c', 'd', '\0'}; //有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); //输出abcd
 
	return 0;
}

//正确
int main(int argc, char *argv[])
{
	char arr1[4] = "****"; //有'\0'结尾
	char arr2[4] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); //输出:abcd

	return 0;
}

这里的代码会出错,'\0'是停止拷贝的终止条件,arr2字符数组所在的内存空间后面存储的内容并不知道,不遇到 '\0' 拷贝就不会停止,这就会导致越界访问,程序就会出现问题。

3.目标空间必须足够大,以确保能放源字符串

#include 
#include 
 
int main(int argc, char *argv[])
{
    //字符数组内存从小到大连续存储(节约内存)
	char arr1[5] = "*****";
	char arr2[] = "hello world";
	printf("%s\n", strcpy(arr1, arr2));
 
	return 0;
}

这里虽然拷贝成功并将结果输出了,但程序却崩溃了。目标空间太小,不足以放置拷贝的源字符串,会造成溢出的情况。

4.目标空间必须可变

#include 
#include 
 
int main(int argc, char *argv[])
{
	char *str1 = "hello world";
	char str2[10] = "*********";
	printf("%s\n", strcpy(str1, str2));
 
	return 0;
}

这里的程序也出现了错误。str1指向的是常量字符串,是不可以被修改掉的,目标空间必须是可以被修改的,因为要将拷贝的字符串放在目标空间中。而源字符串可以是能够修改的、也可以是不能修改的,因为strcpy函数的第二个参数已经用const关键字修饰了,保证了拷贝过程中不会被修改。

5. arr1如果是指针,必须要有指向,且指向的内容必须可改变

#include 
#include 
#include 

//正确
int main(int argc, char *argv[])
{
	char str[10] = "aa";
	char *arr1 = str; //字符数组内存从小到大连续存储(节约内存) arr1以str为依据
	char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); //输出:abcdaa

	return 0;
}

//错误
int main(int argc, char *argv[])
{
	char *arr1; //或 char *arr1 = NULL;
	char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

//正确
int main(int argc, char *argv[])
{
	char *arr1 = malloc(sizeof(4));
	char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾
	printf("%s\n", strcpy(arr1, arr2)); //输出:abcd
	return 0;
}

//正确
int main(int argc, char *argv[])
{
	const char *s = "hello"; //s指向6个字节大小的字符串
	char *p = malloc(strlen(s)); //分配5个字节
	printf("%s\n", strcpy(p, s)); //输出hello
	printf("%ld\n", sizeof("hello"));
	return 0;
}

3) strlen:用来求出s指向的字符串的长度,并返回字符串长度(是指字符串里面包含的字符个数(不算后面'\0'))遇到'\0'才结束         %ld

    strlen(字符数组)                  字符'\0'的ASCII值是0, 字符'0'的ASCII值是48
            NAME
                 strlen - calculate the length of a string

           SYNOPSIS
              
#include
               size_t strlen(const char *s);

          char ch1[5] = {'a', 'b', 'c'};                                char ch1[] = {'a', 'b', 'c'};
          char ch2[3] = {'a', 'b', 'c'};                                char ch2[3] = {'a', 'b', 'c'};
          printf("%ld", strlen(ch1));  //
3                         printf("%ld", strlen(ch1));  // 6                                                                                                          

       char ch1[ ] = {'a', 'b', 'c'};  

       strlen(ch1);  //由于ch数组没有字符串结束符,长度不能确定

       char ch2[10] = {'a', 'b', 'c'};  

       strlen(ch2);  //3

 4) strcat/strncat    ---------字符串连接函数                 strcat(字符数组1,字符数组2)                    NAME
            strcat, strncat - concatenate two strings

        SYNOPSIS
         
  #include                                                                                                        char *strcat(char *dest, const char *src);

            strcat : 用来把src指向的字符串拷贝到dest指向的字符串的末尾(覆盖掉'\0')
                @dest:指向用来保存字符串的内存空间,注意空间大小要足够
                @src:指向要拷贝的字符串数据
            返回值:
                如果成功,返回拷贝后字符串的首地址, 即 dest
                如果失败,返回 NULL

            注意:strcat函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出.
                     strncat正是为了修复strcat的bug的,它的功能和strcat相似,

            char *strncat(char *dest, const char *src, size_t n);
                @dest:指向用来保存字符串的内存空间
                @src:指向要拷贝的字符串数据
                @n:最多拷贝n个字节
            返回值:
                如果成功,返回拷贝后字符串的首地址, 即 dest
                如果失败,返回 NULL

           例:

                  char ch1[] = "abcd";

                  char ch2[] = "sscc";

                  printf("%s\n", strcat(ch1,ch2));  // abcdsscc

   5) strcmp/strncmp  ---------   字符串比较函数                  strcmp(字符串1,字符串2)                                                                  
               那么字符串怎么比较呢?
           
   都是从第一个字符开始比较,一个字符接着一个字符比较ASCII码值的大小
                if c1 > c2, 则返回 整数
                if c1 < c2, 则返回 负数
                if c1 == c2, 则继续比较下一个,如果全部相等才返回 0

        1)在英文字典中位置在后面的ASCII为“大”

        2)小写字母比大写字母“大”

                    char ch1[4] = {'a', 'b', 'c'};

                    char ch2[4] = {'a', 'b', 'c', 'a'};

                    printf("%d", strcmp(ch1, ch2));  // -97    ASCII值比较

                

                   char ch1[] = {'a', 'b', 'c'};

                   char ch2[] = {'a', 'b', 'c', 'a'};

                  printf("%d", strcmp(ch1, ch2));  // 98

        NAME
            strcmp, strncmp - compare two strings

        SYNOPSIS
       
    #include                                                                                                                int strcmp(const char *s1, const char *s2);
          注意:strcmp 无bug,因为没有进行写操作
        strncmp:功能和strcmp相似,只不过比较前n个字符
            int strncmp(const char *s1, const char *s2, size_t n);

  6)   #include        

        strlwr函数-----转换为小写的函数     strlwr(字符串)                                                                           将字符串中大写字母换成小写字母

         strupr函数-----转换为大写的函数     strupr(字符串)                                                                         将字符串中大写字母换成小写字母

7)memset-------初始化函数

memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。

#include         
void *memset(void *s, int c, size_t n); 

 s指向要填充的内存块。
 c是要被设置的值。
 n是要被设置该值的字符数。
 返回类型是一个指向存储区s的指针。

需要说明的几个地方
一、不能任意赋值
memset函数是按照字节对内存块进行初始化,所以不能用它将int数组去初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。
其实c的实际范围应该在0~255,因为memset函数只能取c的后八位给所输入范围的每个字节。也就是说无论c多大只有后八位二进制是有效的

=================================================================================================
对于int a[4];
memset(a, -1, sizeof(a)) 与 memset(a, 511, sizeof(a)) 所赋值的结果一样都为-1:
因为 -1 的二进制码(补码)为(11111111 11111111 11111111 11111111);511 的二进制码为(00000000 00000000 00000001 11111111);
后八位均为(11111111),所以数组中的每个字节都被赋值为(11111111)。
注意int占四个字节,例如a[0]的四个字节都被赋值为(11111111),那么a[0](11111111 11111111 11111111 11111111),即a[0] = -1。

二、注意所要赋值的数组的元素类型

#include 
#include 

//例一:对char类型的数组a初始化,设置元素全为’1’
int main(int argc, char *argv[])
{
    char a[4];
    memset(a, '1', 4);
    for(int i = 0; i < 4; i++)
		printf("%c ", a[i]); //输出:1 1 1 1
	putchar('\n');
    return 0;
}

//例二:对int类型的数组a初始化,设置元素值全为1
int main(int argc, char *argv[])
{
    int a[4];
    memset(a, 1, sizeof(a)); //或者memset(a, 1, 16)  0000 0001  0000 0001  0000 0001  0000 0001
    for(int i = 0; i < 4; i++)
		printf("%d ", a[i]); //输出:16843009 16843009 16843009 16843009 
	putchar('\n');
    return 0;
}

int main(int argc, char *argv[])
{
    int a[4];
    memset(a, 1, 4); //0000 0001  0000 0001  0000 0001  0000 0001
    for(int i = 0; i < 4; i++)
		printf("%d ", a[i]); //输出:16843009 0 0 0
	putchar('\n');
    return 0;
}

        例一程序中,数组a是字符型的,字符型占据的内存大小就是1Byte,而memset函数也是以字节为单位进行赋值的,所以输出正确。
        例二程序中,数组a是整型的,整型占据的内存大小为4Byte,而memset函数还是按照字节为单位进行赋值,将1(0000 0001)赋给每一个字节。那么对于a[0]来说,其值为(00000001 00000001 00000001 00000001),即十进制的16843009。

注意:

#include 
#include 

/*
	错误
	当数组作为参数传递时,其传递的实际上是一个指针,这个指针指向数组的首地址,
	如果用sizeof(a)函数得到的只是指针的长度,而不是数组的长度。
*/
void fun1(int a[]) {
    memset(a, -1, sizeof(a)); //sizeof(a)是指针长度
}

int main(int argc, char *argv[]) {
    int a[6];
    fun1(a);
    for(int i = 0; i < 6; i++) {
        printf("%d ", a[i]);
    }
	printf("\n");
    return 0;
}

//正确
void fun1(int a[], int len) {
    memset(a, -1, len); 
}

int main(int argc, char *argv[]) {
    int a[6];
    int len = sizeof(a);
    fun1(a, len);
    for(int i = 0; i < 6; i++) {
        printf("%d ", a[i]);
    }
	printf("\n");
    return 0;
}

8)strdup-----字符串拷贝

一.函数分析

1.函数原型:

#include 
char *strdup(const char *s);

2.功能:

strdup()函数主要是拷贝字符串s的一个副本,由函数返回值返回,这个副本有自己的内存空间,和s没有关联。strdup函数复制一个字符串,使用完后,要使用delete函数删除在函数中动态申请的内存,strdup函数的参数不能为NULL,一旦为NULL,就会报段错误,因为该函数包括了strlen函数,而该函数参数不能是NULL。

3.strdup函数实现

char * __strdup(const char *s) {
   size_t  len = strlen(s) +1;
   void *new = malloc(len);
   if (new == NULL) {
      return NULL;
   }
   return (char *)memcpy(new, s, len);
}

4.函数实例

#include 
#include 

int main(void) {
	char *src = "This is the jibo";
	char *dest;
	dest = strdup(src);
	printf(“the dest %s\n”, dest);
	free(dest);
	return 0;
}

二. strdup与strcpy函数的区别

1.共同点:

        两个函数都实现了字符串的拷贝。

2.不同点:

       1)  strcpy函数:把从src地址开始且含有结束符的字符串复制到以dest开始的地址空间

        2)由strcpy和strdup函数实现可知

  • strdup函数返回指向被复制的字符串的指针,所需空间由malloc()函数分配且可以由free()函数释放。stdrup可以直接把要复制的内容复制给没有初始化的指针,因为它会自动分配空间给目的指针。

  • strcpy的目的指针一定是已经分配好的内存指针。

        3)strdup的缺点:

使用strdup函数的时候,往往会忘记内存的释放,因为申请内存空间的动作是在strdup函数内实现,如果对该函数的实现不是很了解,则会忘记使用free函数来释放空间

你可能感兴趣的:(C语言,c语言)