[笔记]指针与字符串

学完指针与函数,指针与数组后,我接触到了新的数据类型,字符串

在C语言中,字符串是字符的集合,可利用数组(指针常量),或者指针变量来表示

  • char *str1="Apple ipod";

表示str1指向字符串的第一个字符‘A’,我们用图来详细描述

img

也可以用数组的表示法,如下所示:

  • char str2[10]="Apple iPod"

img

str2是一个数组名,表示此数组第一个元素的地址(相当于&str2[0]),str+1表示数组的第二个元素的地址(相当于&str2[1]),........以此类推,str2+10则是字符(‘0’)的地址,字符的最后都有一个空字符(‘0’),作为字符串的结束点,若缺少它,将会产生错误的答案

注意点:str1是指针变量,而str2是指针常量,所以str2不可以使用++(自增)和--(自减)运算符。

首先,利用指针变量指向一个字符串,并以字符的格式将字符串输出

#include<stdio.h>
#include<stdlib.h>
int main()
{
    char* p = "Apple iPod";
     //定义一个指针p,指向字符串Apple iPod的首地址,A的地址
    printf("字符串str为:");
    for (int i = 0; *p != '\0'; i++)
    {
        printf("%c", *p);
                //结束条件为,输出到(\0)时
        p++;
    }
    return 0;

}

利用循环输出目前指针所指向的字符,直到空字符(‘0’)为止,上例的字符串也可以使用数组的方式加以输出。

在放代码前补一个for循环的小笔记:for(循环初始值,循环判定条件,循环运算)

三个条件三个空间,互不干涉均可省略!

#include<stdio.h>
#include<stdlib.h>
int main()
{
    char p[] = "Apple iPod" ;
     //定义一个指针p,指向字符串Apple iPod的首地址,A的地址
    printf("字符串str为:");
    for (int i = 0; p[i] != '\0'; i++)
    {
        printf("%c", p[i]);
    }
    return 0;

}

上列代码是我们常用的一种形式,将判断条件改为了p[i]!=0,数组走到‘0’元素时就停止了循环

以上代码输出均为:Apple iPod

其实平常我们最常用,最简单的方法,就是使用%s格式进行输出

#include<stdio.h>
#include<stdlib.h>
int main()
{
    char p[] = { "Apple iPod" };
    char* p2 = { "Apple iPod" };

    printf("%s\n", p);
    printf("%s\n", p2);
    
    return 0;

}

可以看到,数组和指针均可用花括号括元素表示,输出结果:

img

要注意的是,此时变量给予的数组名,如p和p2,因为数组名表示数组第一个元素的地址,所以会从此地址一直打印到字符串的结束点(空字符‘0’)

若将上例代码的p数组元素个素修改为10,将会出错,因为无法找到0,看代码

img

输出结果:

img

在输出结果中,p的字符串是错误的,在输出完后乱码,因为分配给p的空间不足,使他无法存放空字符(‘0’),所以经常会将数组下标省略,有程序去自定义

除了在定义字符串变量时,直接给予一个字符串外,也可以利用其他方式得到

上代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{

    char* p = { "Learning pointer now" };
    char p2[] = "Go ahead";
    char* p3;
    char p4[20];

    p3 = "I want to buy a iPod\0";
    printf("C or C++:");

    scanf("%s", p4);
    printf("\n%s\n", p);
    printf("%s\n", p2);
    printf("%s\n", p3);
    printf("%s\n", p4);

    return 0;

}
  • p为指针变量,定义时指定一个字符串
  • p2为指针常量,为数组,定义时候给予一个字符串
  • p3为指针变量,通过赋值给它指定一个字符串
  • p4为指针常量,为数组,通过scanf键盘输入给它一个字符串

注意,若将代码中的p3改为数组,p4改为指针变量,则会产生错误

img

img

p3为一个数组,它是指针常量,则计算机已经给p3分配好固定的地址,不可以再指定一个新的字符串给它

p4是一个指针变量,不可以由输入的方式来完成指定操作!

指针模拟string库

字符串的操作,最常用的包括:

  • 计算字符串长度(string length)
  • 进行字符串复制(string copy)
  • 进行字符串链接(string concatenate)
  • 进行字符串比较(string compare)

要完成这些动作,最方便的是调用字符串的库函数,弹药包含string.h的头文件,这些函数原型全在string的头文件内

除了调用字符串的库函数外,也能利用字符串指针自行编写程序,以模拟这些库函数的功能

计算字符串长度:

字符串的长度表示此字符串有多少个字符,但不包括空字符("0"),计算字符串长度的库函数为strlen,其语法如下:

strlen(str1);//计算str1的长度

我们用指针来实现它:

  • 定义一个指针指向一个字符串的首地址
  • 自定义函数,将指针传入函数内
  • 利用循环语句,指针从首地址指向包含'0'的地址
  • 指针每走一次,长度加一,直到走到'0'
#include<stdio.h>
#include<string.h>
int stringlength(char*);
int main()
{
    int length = 0;
    char* p = "Apple iPod";
    printf("使用库函数:%d\n", strlen(p));

    length = stringlength(p);
    printf("使用自定义函数:%d\n", length);

    return 0;
}
stringlength(char* p)
{
    int i = 0;//计数功能
    while (*p != '\0')
        //当指针递增到存有'\0'字符地址时,停止循环
    {
        p++;
        i++;
    }

    return i;
    //返回i
}

运行结果:

img

在stringlenth函数内,若*p不为空字符,则变量i加1,之后指针再走向下一个字符的地址,上述操作持续进行,直到*为空字符则结束,因为空字符是字符串的结束字符

字符串的复制:

字符串复制的库函数为strcpy,其语法如下:

strcpy(str1,str2);

表示从str2字符串的第一个字符,逐一的赋值到str1字符串,注意:第一个参数str1是接收者,第二个参数str2是给予者

来看下它的使用:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    char* p = "Apple iPod";
    char a[80] = {" "};
    printf("使用库函数之前: \np=%s\na=%s\n",p,a);
    
    strcpy(a, p);

    printf("使用库函数之后:\np=%s\na=%s\n", p, a);

    system("pause");
    return 0;

}

来看看结果:

img

若a数组内原来就有字符,则原有字符会被指针p指向的字符串所代替,这里必须小心,以免数据丢失

我们用图片来解释一下它的替代过程:

img

p指向了字符串的首地址,指针p内存储的是字符‘A’的地址,定义数组时分配了80个char类型的存储空间,由于字符串存储地址具有连续性,则在循环过程中逐个复制了下来

这里特别注意:a数组中要有足够的空间给容纳s字符串

若将上例代码中的

char a[80]=" ";

改为:

char *a

将会有错误产生,因为*a是一个指针变量,且没有一个指向的内存空间,这是我经常会犯错的地方,解决办法就是先分配一个内存给a指针变量,用到malloc来分配

a=(char*)malloc(sizeof*(char));

但是就这样改变的话会发生“访问写入权限冲突”具体解决方案请移步到补充笔记

img

接下来我们继续参照字符串复制的原理,用自定义函数实现

基本思路:

  • 定义一个指针指向字符串首地址
  • 定义一个能容纳字符串的数组
  • 利用循环让指针从首字符依次走到空字符(0)
  • 每走一次将指针走过的字符写入数组内
  • 具有连续性
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringCopy(char*, char*);
int main()
{
    char* p = "Apple iPod";
    char a[80] = " ";
    printf("传入之前:\np=%s\na=%s\n", p, a);
    StringCopy(p, a);
    printf("写入之后:\np=%s\na=%s\n", p, a);

    system("pause");
    return 0;

}
void StringCopy(char* p, char* a)
{
    while (*a != '\0')
    {
        *a = *p;
        p++;
        a++;
    }
}

在StringCopy函数中,利用while循环,将*p的内容赋给*a的内容,待传入到空字符则结束循环,若不是空字符,则将p和a都移步到下一地址,继续执行复制的动作

运行结果:

img

若我们只需要复制n个字符时候,则调用strning函数,其语法如下:

strning(a,p,n)//将p中的字符串复制到a数组内,复制个数为n

  • 定义一个指针指向字符串首地址
  • 定义一个能容纳字符串的数组
  • 将需要断开的地址赋值为空字符
  • 当循环走到空字符时,停止复制

我们用指针来实现它的原理:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringCopy(char*, char*);
int main()
{
    char* p = "Apple iPod";
    char a[80];
    a[5] = '\0';

    strncpy(a, p, 5);
    printf("使用库函数:\np=%s\na=%s\n", p, a);
    
    StringCopy(p, a);
    printf("使用自定义函数:\np=%s\na=%s\n", p, a);

    system("pause");
    return 0;

}
void StringCopy(char* p, char* a)
{
    int i = 0;
    while (i < 5 && *a != '\0')
    {
        *a = *p;
        p++;
        a++;
                i++;
    }
}

循环条件需要满足i<5,则循环要走五次,还要满足这五次循环中,a的值始终不能为空字符

因为是复制前5个字符,控制循环体的条件可缺一,不过最好全部加上,这样显得目的明确:

img

img

与上例代码其实大致相同,只不过多了变量i来控制循环体,同样,如果需要复制n个字符,只需要在下标n处设置空字符即可。

字符串链接:

字符串的链接的库函数为strcat,其语法如下:

strcat(str1,str2);

表示将str2字符串连接到str1字符串的尾端,注意,str1必须要分配足够的空间

  • 定义两个需要链接的字符
  • 注意需要嵌入字符串的内存要足够容纳需要嵌入的字符串
  • 利用循环先走到需要嵌入的字符串尾端,不包含空字符
  • 利用循环将嵌入的字符串嵌入进数组内
  • 尾端的空字符会被代替
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringConcat(char*, char*);
int main()
{
    char a[80] = "I want to buy a ";
    char* p = "iPhone";
    char b[80] = "I want to buy a ";

    strcat(a, p);
    printf("使用库函数:%s\n", a);

    StringConcat(b, p);
    printf("使用自定义函数:%s\n", a);

    system("pause");
    return 0;
}
void StringConcat(char* b, char* p)
{
    
    while (*b != '\0')
    {
        b++;
    }
    while (*b != '\0');
    {
        *b = *p;
        b++;
        p++;
    }
}

那么这时我产生了一个疑问,能不能将数组嵌入到p后面呢?通过测试,是不行的

img

我用的是strcat函数直接进行链接,因为p为指针变量,它指向的为一个地址

给它指定一个字符串,它所含的值也是字符串首字符的地址,它可以通过运算走过相邻的地址,但是它无法在所指向之外的地址存入值,所以无法完成连接

而数组可以是因为在数组定义的时候,计算机并分配了n个连续性同类型的内存空间给它,它所有的地址都是相邻的,已经分配好属于它的,所以在未越界的情况下,可以在地址内存储,修改值

在上述代码的stringconcate函数中,利用循环先追踪到a所存储的字符串尾端,接下来,将p指针指向的字符串(p)指定给(a),直到(*p)是空字符为止

img

若只是要链接n个字符,则可调用strncat库函数,其语法如下:

strncat(str1,str2,n);

意思是将str2的前n个字符链接到str1

与上述思路一样,我们利用循环次数来控制复制量

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringConcat(char*, char*, int);
int main()
{
    char a[80] = "I want to buy a ";
    char* p = "iPhone";
    char b[80] = "I want to buy a ";

    strncat(a, p, 5);
    printf("使用库函数:%s\n", a);

    StringConcat(b, p, 5);
    printf("使用自定义函数:%s\n", a);

    system("pause");
    return 0;
}
void StringConcat(char* b, char* p, int n)
{
    int i = 0;
    while (*b != '\0')
    {
        b++;
    }
    while (i < n && *b != '\0')
    {
        *b = *p;
        b++;
        p++;
    }
}

运行结果:

img

字符串比较

字符串比较也可以说是最常用的,若要比较两个字符串是否相等,有些人可能会这样编写代码

#include<stdio.h>
#include<stdlib.h>
int main()
{
    char str1[] = "Honda Civic";
    char str2[] = "Honda Civic";
    
    printf("str1指向的地址:%x\n", str1);
    printf("str2指向的地址:%x\n", str2);

    if (str1 == str2)
    {
        printf("str1等于str2\n");
    }
    else
    {
        printf("str1不等于str2\n");
    }
    system("pause");
    return 0;
}

这段代码是在比较str1和str2的地址是否相等,而不是比较内容是否相等,因为str1和str2是数组名,表示第一个元素的地址,所以这段内容并不是在比较字符串的内容

输出结果:

img

如果使用指针变量作比较,这会有不一样的结果:

#include<stdio.h>
#include<stdlib.h>
int main()
{

    char* p = "Hello Word";
    char* p2 = "Hello Word";
    char* p3 = "Hello Jiajia";

    printf("p所指向的地址:%x\n", p);
    printf("p2所指向的地址:%x\n", p2);
    printf("p3所指向的地址:%x\n", p3);

    if (p == p2)
    {
        printf("p等于p2\n");
    }
    else
    {
        printf("p不等于p2\n");
    }

    if (p == p3)
    {
        printf("p等于p3\n");
    }
    else
    {
        printf("p不等于p3\n");
    }
    system("pause");
    return 0;
}

上述代码也是在比较地址,因为p,p2,p3均为指针变量,指向的都是一段字符串的首地址,p和p2指向的是相同的字符串,则它两相等,而p3指向的则是不同的字符串,所以它与p和p2都不想等

也可以使用malloc动态内存分配函数,先分配内存,再指定一个字符串给它,如:

img

输出结果也与上述代码相同

img

但我们一般不会使用上述两种方式来比较字符串是否相等,这里的用意是为了了解这样比较的方法是错误的,也更加加强了指针与字符串之间的关系,

当然是用strcmp函数来比较字符串是最正统的,strcmp函数在头文件string函数内,其语法如下:

strcmp(p,p2);//表示比较p与p2字符串是否相等

我们用代码来看看:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int stringCompare(char*, char*);
int main()
{
    char* p = "Hello Word";
    char* p2 = "Hello China";
    int value;

    value = strcmp(p, p2);

    printf("使用库函数比较:\n");

    strcmp(p, p2);

    switch (value)
    {
    case 0:
        printf("p等于p2\n");
        break;
    case 1:
        printf("p大于p2\n");
        break;
    case -1:
        printf("p小于p2\n");
        break;
    }

    printf("使用自定义函数比较:\n");

    value = stringCompare(p, p2);

    if (value == 0)
    {
        printf("p等于p2\n");
    }
    else if (value > 0)
    {
        printf("p大于p2\n");
    }
    else
    {
        printf("p小于p2\n");
    }
    system("pause");
    return 0;
}
int stringCompare(char* x, char* y)
{

    //让指针走过字符串每一个字符
    //当字符串字符不相等时停止循环
    while (*x == *y)
    {
        if (*x == '\0')
            //判断字符串是否为0
        {
            return 0;
        }
        x++;
        y++;
    }
    return *x - *y;

}

我们来用图剖析自定义函数体内的代码

img

img

在stringcompare函数中,比较x与y的内容是否相等,假如相等,则再判断*x是否为空字符,若不是,则将*x与*y的内容相减后返回

输出结果:

img

在循环到空格的时候,x++,y++指向对应的字母分别为W和C,转换成ASCII码做比较,W为87,C为67,则p大于p2

有时在比较两个字符串时,并不需要比较整个字符串,只要比较前面几个字符即可,可用到strncmp函数,其格式为

strncmp(str1,str2,n)//比较str1与str2前五个字符的大小

我们用代码来实现:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int stringCompare(char*, char*, int n);
int main()
{
    char* p = "Hello Word";
    char* p2 = "Hello China";
    int value;

    value = strncmp(p, p2,5);

    printf("使用库函数比较:\n");

    strcmp(p, p2);

    switch (value)
    {
    case 0:
        printf("p等于p2\n");
        break;
    case 1:
        printf("p大于p2\n");
        break;
    case -1:
        printf("p小于p2\n");
        break;
    }

    printf("使用自定义函数比较:\n");

    value = stringCompare(p, p2, 5);

    if (value == 0)
    {
        printf("p等于p2\n");
    }
    else if (value > 0)
    {
        printf("p大于p2\n");
    }
    else
    {
        printf("p小于p2\n");
    }
    system("pause");
    return 0;
}
int stringCompare(char* x, char* y, int n)
{

    //让指针走过字符串每一个字符
    //当字符串字符不相等时停止循环
    int i = 0;
    while (i < n && *x == *y)
    {
        if (*x == '\0')
            //判断字符串是否为0
        {
            return 0;
        }
        x++;
        i++;
        y++;
    }
    return *x - *y;

}

与复制,连接一样,通过循环变量来控制循环次数(比较次数),可以看到

img

多了变量i和参数n,起到一个控制循环次数的作用,输出结果:

img

总结:

指针与字符串我们清楚了指针常量和变量的区别和关系

数组可看做指针常量,在定义的时候,计算机就已经分配好了固定的内存给它,这段地址是不可修改,只能存储

当一个字符串赋值给数组时,是将字符串每个字符存入数组内的空间

当一个字符串赋值给指针时,是将指针指向该字符串的首地址,如果在定义指针时并未指定,则不可通过赋值给指针

如果一定要赋值,则需要动态分配一片内存给指针,再指定一个字符串给它

当需要用户输入的时候,指针不可以指定键盘输入的元素,俗点说,指针只能指向已知的数据,否则会像无头苍蝇一样。如果需要用户输入,则动态malloc分配一片内存给指针

而数组可以,因为数组已经是一片固定内存,当用户输入时,用户输入的数据会存入该数组。

数组相当于一个公用箱,你不能动它,但是你可以存取东西,而指针相当于一个私人用箱,你可以随意修改己值和他值。当然,指向的数据类型需要是变量。

指针*和数组[]均有取值功能。

在本章中模拟string库函数,原理不过是通过指针去修改,存储地址内的值

  • 复制就是指针走过字符串所有字符,然后依次将其存入未被占用的地址内
  • 连接就是指针走过所有字不包含空的字符,然后在尾端执行复制
  • 比较就是指针依次走过所有字符,将字符转换成ASCII码进行比较
  • 计数就是指针依次走过所有字符,每走过一个,计数变量递增一次

本文链接:

https://nullcode.fun/109.html
1 + 7 =
快来做第一个评论的人吧~