MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言指针操作字符串函数的应用

2021-10-214.7k 阅读

字符串与指针基础概念

在C语言中,字符串是由字符组成的序列,并且以空字符'\0'作为结束标志。指针则是一种变量,它存储的是另一个变量的内存地址。由于字符串在内存中是以字符数组的形式存储的,而数组名本质上就是指向数组首元素的指针,这使得指针在操作字符串时有着天然的优势。

字符串的存储方式

字符串通常有两种常见的存储方式:字符数组和字符指针。

字符数组存储字符串

char str1[] = "Hello, World!";

在上述代码中,str1是一个字符数组,编译器会自动在字符串末尾添加'\0'。数组的大小为字符串长度加上1,以容纳结束符。

字符指针指向字符串

char *str2 = "Hello, World!";

这里str2是一个字符指针,它指向了一个存储在只读数据段的字符串常量。虽然看起来和字符数组类似,但本质上有区别。str2只是一个指针变量,它存储的是字符串首字符的地址,而不是字符串本身。

C语言标准库中的字符串操作函数与指针应用

C语言标准库提供了一系列用于操作字符串的函数,这些函数大多都利用了指针来实现高效的字符串处理。

strlen函数

strlen函数用于计算字符串的长度,不包括结束符'\0'。其原型如下:

size_t strlen(const char *s);

该函数通过遍历字符串,直到遇到'\0'来确定字符串的长度。实现原理是利用指针不断移动并计数。

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    size_t len = strlen(str);
    printf("The length of the string is: %zu\n", len);
    return 0;
}

在上述代码中,strlen函数接收字符数组名str,由于数组名可作为指针使用,函数内部通过指针移动遍历字符串,统计字符个数,直到遇到'\0'

strcpy函数

strcpy函数用于将一个字符串复制到另一个字符串中。其原型为:

char *strcpy(char *dest, const char *src);

函数从源字符串src的起始位置开始,逐个字符复制到目标字符串dest,直到遇到'\0',并将'\0'也复制过去。

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello";
    char dest[20];
    strcpy(dest, src);
    printf("Copied string: %s\n", dest);
    return 0;
}

在这个例子中,strcpy函数使用指针操作,将src字符串内容复制到dest数组。函数内部通过指针src逐个读取源字符串字符,通过指针dest逐个写入目标字符串,直到遇到'\0'

strcat函数

strcat函数用于将一个字符串连接到另一个字符串的末尾。其原型为:

char *strcat(char *dest, const char *src);

函数首先找到目标字符串dest的末尾(即'\0'的位置),然后从该位置开始将源字符串src复制到dest,最后在连接后的字符串末尾添加'\0'

#include <stdio.h>
#include <string.h>

int main() {
    char dest[20] = "Hello, ";
    char src[] = "World!";
    strcat(dest, src);
    printf("Concatenated string: %s\n", dest);
    return 0;
}

在此代码中,strcat函数利用指针先定位dest字符串的结束位置,然后使用指针将src字符串内容追加到dest末尾,实现字符串连接。

strcmp函数

strcmp函数用于比较两个字符串。其原型为:

int strcmp(const char *s1, const char *s2);

函数从两个字符串的首字符开始逐个比较,直到遇到不同字符或'\0'。如果两个字符串相等,返回0;如果s1小于s2,返回负数;如果s1大于s2,返回正数。

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "apple";
    char str2[] = "banana";
    int result = strcmp(str1, str2);
    if (result < 0) {
        printf("str1 is less than str2\n");
    } else if (result > 0) {
        printf("str1 is greater than str2\n");
    } else {
        printf("str1 is equal to str2\n");
    }
    return 0;
}

上述代码中,strcmp函数通过指针操作,逐个比较str1str2的字符,根据比较结果返回相应的值,从而判断两个字符串的大小关系。

自定义字符串操作函数与指针应用

除了标准库函数,我们还可以自定义一些字符串操作函数,进一步理解指针在字符串处理中的应用。

自定义strlen函数

#include <stdio.h>

size_t myStrlen(const char *s) {
    size_t len = 0;
    while (*s != '\0') {
        len++;
        s++;
    }
    return len;
}

int main() {
    char str[] = "Hello, World!";
    size_t len = myStrlen(str);
    printf("The length of the string is: %zu\n", len);
    return 0;
}

myStrlen函数中,通过指针s遍历字符串,每次移动指针并增加计数器len,直到遇到'\0',返回字符串长度。这和标准库的strlen函数原理相同,体现了指针在计算字符串长度中的应用。

自定义strcpy函数

#include <stdio.h>

char *myStrcpy(char *dest, const char *src) {
    char *originalDest = dest;
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';
    return originalDest;
}

int main() {
    char src[] = "Hello";
    char dest[20];
    myStrcpy(dest, src);
    printf("Copied string: %s\n", dest);
    return 0;
}

myStrcpy函数中,使用指针src读取源字符串字符,指针dest写入目标字符串。通过循环逐个复制字符,直到src指向'\0',最后在目标字符串末尾添加'\0'。函数返回目标字符串起始地址,和标准库strcpy函数行为一致。

自定义strcat函数

#include <stdio.h>

char *myStrcat(char *dest, const char *src) {
    char *originalDest = dest;
    while (*dest != '\0') {
        dest++;
    }
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';
    return originalDest;
}

int main() {
    char dest[20] = "Hello, ";
    char src[] = "World!";
    myStrcat(dest, src);
    printf("Concatenated string: %s\n", dest);
    return 0;
}

myStrcat函数首先利用指针找到目标字符串dest的末尾,然后从该位置开始,使用指针将源字符串src的字符逐个复制到dest,最后添加'\0'。这与标准库strcat函数的实现思路相同,展示了指针在字符串连接中的作用。

自定义strcmp函数

#include <stdio.h>

int myStrcmp(const char *s1, const char *s2) {
    while (*s1 != '\0' && *s2 != '\0') {
        if (*s1 < *s2) {
            return -1;
        } else if (*s1 > *s2) {
            return 1;
        }
        s1++;
        s2++;
    }
    if (*s1 == '\0' && *s2 == '\0') {
        return 0;
    } else if (*s1 == '\0') {
        return -1;
    } else {
        return 1;
    }
}

int main() {
    char str1[] = "apple";
    char str2[] = "banana";
    int result = myStrcmp(str1, str2);
    if (result < 0) {
        printf("str1 is less than str2\n");
    } else if (result > 0) {
        printf("str1 is greater than str2\n");
    } else {
        printf("str1 is equal to str2\n");
    }
    return 0;
}

myStrcmp函数通过指针*s1*s2逐个比较两个字符串的字符。如果字符不同,根据字符的ASCII码值返回相应的比较结果。如果遍历完两个字符串且字符都相同,则返回0。如果一个字符串先结束,则根据结束情况返回相应值,实现了字符串比较功能,展现了指针在字符串比较中的应用。

指针操作字符串时的注意事项

内存分配与释放

当使用指针操作字符串时,特别是在动态分配内存的情况下,需要注意内存的分配与释放。例如,使用malloc分配内存来存储字符串:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *str = (char *)malloc(20 * sizeof(char));
    if (str == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    strcpy(str, "Hello, World!");
    printf("%s\n", str);
    free(str);
    return 0;
}

在这个例子中,使用malloc分配了20个字符大小的内存空间,然后使用strcpy将字符串复制进去。最后,使用free释放分配的内存,避免内存泄漏。如果忘记释放内存,随着程序运行,内存会逐渐被耗尽,导致程序出现内存相关的错误。

指针越界

指针越界是在操作字符串时容易出现的问题。例如,在使用字符数组存储字符串时,如果访问数组越界,可能会导致未定义行为。

#include <stdio.h>

int main() {
    char str[] = "Hello";
    char ch = str[10]; // 越界访问
    printf("%c\n", ch);
    return 0;
}

在上述代码中,str数组的有效索引范围是0到4(包含结束符'\0'),访问str[10]超出了这个范围,这会导致未定义行为,程序可能崩溃或产生错误的结果。

字符串常量与指针

当使用字符指针指向字符串常量时,要注意字符串常量存储在只读数据段,不能通过指针修改其内容。

#include <stdio.h>

int main() {
    char *str = "Hello";
    *str = 'h'; // 试图修改字符串常量,会导致未定义行为
    printf("%s\n", str);
    return 0;
}

在这段代码中,str指向一个字符串常量,尝试通过*str修改首字符会导致未定义行为,程序可能崩溃。如果需要修改字符串内容,应该使用字符数组或动态分配内存。

函数参数传递

在将字符串作为参数传递给函数时,要注意函数参数的声明。例如,如果函数期望接收一个指向字符串的指针,传递字符数组名是没问题的,因为数组名会自动转换为指针。

#include <stdio.h>

void printString(const char *s) {
    printf("%s\n", s);
}

int main() {
    char str[] = "Hello, World!";
    printString(str);
    return 0;
}

在上述代码中,printString函数接收一个指向字符串的指针,main函数中传递字符数组名str,这是合法的,因为数组名在这种情况下会被当作指针处理。但如果函数声明和实际传递的参数类型不匹配,可能会导致编译错误或运行时错误。

复杂字符串操作中的指针应用

字符串中查找子字符串

在字符串中查找子字符串是一个常见的操作。可以利用指针实现这个功能。

#include <stdio.h>
#include <string.h>

char *findSubstring(const char *str, const char *sub) {
    while (*str != '\0') {
        const char *p1 = str;
        const char *p2 = sub;
        while (*p1 != '\0' && *p2 != '\0' && *p1 == *p2) {
            p1++;
            p2++;
        }
        if (*p2 == '\0') {
            return (char *)str;
        }
        str++;
    }
    return NULL;
}

int main() {
    char str[] = "Hello, World! This is a test.";
    char sub[] = "is a";
    char *result = findSubstring(str, sub);
    if (result != NULL) {
        printf("Substring found at: %s\n", result);
    } else {
        printf("Substring not found\n");
    }
    return 0;
}

findSubstring函数中,使用两个指针p1p2分别遍历主字符串str和子字符串sub。通过不断移动指针来比较字符,如果找到匹配的子字符串,则返回主字符串中该子字符串的起始位置。如果遍历完主字符串都没有找到匹配项,则返回NULL

字符串替换

实现字符串替换功能也可以借助指针操作。

#include <stdio.h>
#include <string.h>

void replaceSubstring(char *str, const char *sub, const char *replace) {
    char *p = findSubstring(str, sub);
    if (p != NULL) {
        int subLen = strlen(sub);
        int replaceLen = strlen(replace);
        char *end = str + strlen(str);
        // 移动后面的字符,为替换字符串腾出空间
        while (end >= p + subLen) {
            *(end + replaceLen - subLen) = *end;
            end--;
        }
        // 复制替换字符串
        for (int i = 0; i < replaceLen; i++) {
            *(p + i) = replace[i];
        }
    }
}

int main() {
    char str[] = "Hello, World! This is a test.";
    char sub[] = "is a";
    char replace[] = "was a";
    replaceSubstring(str, sub, replace);
    printf("Replaced string: %s\n", str);
    return 0;
}

replaceSubstring函数中,首先调用findSubstring函数找到需要替换的子字符串位置。然后根据子字符串和替换字符串的长度,移动主字符串中后面的字符,为替换字符串腾出空间。最后将替换字符串复制到相应位置,实现字符串替换功能。

字符串分割

字符串分割是将一个字符串按照指定的分隔符拆分成多个子字符串。

#include <stdio.h>
#include <string.h>

#define MAX_TOKENS 10
#define MAX_TOKEN_LEN 50

void tokenize(const char *str, const char *delim) {
    char tokens[MAX_TOKENS][MAX_TOKEN_LEN];
    const char *p = str;
    int tokenIndex = 0;
    while (*p != '\0' && tokenIndex < MAX_TOKENS - 1) {
        int len = 0;
        while (*p != '\0' && strchr(delim, *p) == NULL && len < MAX_TOKEN_LEN - 1) {
            tokens[tokenIndex][len++] = *p++;
        }
        tokens[tokenIndex][len] = '\0';
        tokenIndex++;
        while (*p != '\0' && strchr(delim, *p) != NULL) {
            p++;
        }
    }
    tokens[tokenIndex][0] = '\0';
    for (int i = 0; i < tokenIndex; i++) {
        printf("Token %d: %s\n", i + 1, tokens[i]);
    }
}

int main() {
    char str[] = "apple,banana,orange;grape";
    char delim[] = ",;";
    tokenize(str, delim);
    return 0;
}

tokenize函数中,通过指针p遍历字符串str。当遇到分隔符时,将之前的字符作为一个子字符串存储到tokens数组中。通过不断移动指针,跳过分隔符并继续寻找下一个子字符串,实现字符串分割功能,并输出分割后的各个子字符串。

指针在字符串格式化与解析中的应用

sprintf函数与指针

sprintf函数用于将格式化的数据写入字符串。其原型为:

int sprintf(char *str, const char *format, ...);
#include <stdio.h>

int main() {
    char str[50];
    int num = 123;
    char name[] = "John";
    sprintf(str, "Name: %s, Number: %d", name, num);
    printf("%s\n", str);
    return 0;
}

在上述代码中,sprintf函数使用指针将格式化后的内容写入字符数组strformat字符串指定了输出的格式,通过指针操作将namenum按照格式写入str,实现字符串的格式化构建。

sscanf函数与指针

sscanf函数用于从字符串中读取格式化的数据。其原型为:

int sscanf(const char *str, const char *format, ...);
#include <stdio.h>

int main() {
    char str[] = "Name: John, Number: 123";
    char name[20];
    int num;
    sscanf(str, "Name: %s, Number: %d", name, &num);
    printf("Name: %s, Number: %d\n", name, num);
    return 0;
}

在这段代码中,sscanf函数通过指针操作从字符串str中按照指定格式读取数据。format字符串指定了读取的格式,将读取到的数据存储到name数组和num变量中,实现字符串的解析功能。

自定义字符串格式化函数

我们也可以自定义类似sprintf的字符串格式化函数,进一步理解指针在其中的应用。

#include <stdio.h>

void mySprintf(char *dest, const char *format, ...) {
    char *pDest = dest;
    const char *pFormat = format;
    va_list args;
    va_start(args, format);
    while (*pFormat != '\0') {
        if (*pFormat == '%') {
            pFormat++;
            switch (*pFormat) {
                case 's': {
                    char *str = va_arg(args, char *);
                    while (*str != '\0') {
                        *pDest++ = *str++;
                    }
                    break;
                }
                case 'd': {
                    int num = va_arg(args, int);
                    char temp[20];
                    int i = 0;
                    if (num < 0) {
                        *pDest++ = '-';
                        num = -num;
                    }
                    do {
                        temp[i++] = num % 10 + '0';
                        num /= 10;
                    } while (num > 0);
                    for (int j = i - 1; j >= 0; j--) {
                        *pDest++ = temp[j];
                    }
                    break;
                }
                default:
                    *pDest++ = *pFormat;
                    break;
            }
        } else {
            *pDest++ = *pFormat;
        }
        pFormat++;
    }
    *pDest = '\0';
    va_end(args);
}

int main() {
    char str[50];
    int num = 123;
    char name[] = "John";
    mySprintf(str, "Name: %s, Number: %d", name, num);
    printf("%s\n", str);
    return 0;
}

mySprintf函数中,使用指针pDest指向目标字符串destpFormat指向格式化字符串format。通过va_list处理可变参数。当遇到'%'时,根据后续字符判断格式化类型,如's'处理字符串,'d'处理整数,通过指针操作将相应数据按照格式写入目标字符串,实现自定义的字符串格式化功能。

自定义字符串解析函数

同样,我们可以自定义类似sscanf的字符串解析函数。

#include <stdio.h>
#include <ctype.h>

int mySscanf(const char *str, const char *format, ...) {
    const char *pStr = str;
    const char *pFormat = format;
    va_list args;
    va_start(args, format);
    int count = 0;
    while (*pFormat != '\0' && *pStr != '\0') {
        if (*pFormat == '%') {
            pFormat++;
            switch (*pFormat) {
                case 's': {
                    char *dest = va_arg(args, char *);
                    while (!isspace(*pStr) && *pStr != '\0') {
                        *dest++ = *pStr++;
                    }
                    *dest = '\0';
                    count++;
                    break;
                }
                case 'd': {
                    int *dest = va_arg(args, int *);
                    int num = 0;
                    int sign = 1;
                    if (*pStr == '-') {
                        sign = -1;
                        pStr++;
                    }
                    while (isdigit(*pStr)) {
                        num = num * 10 + (*pStr - '0');
                        pStr++;
                    }
                    *dest = num * sign;
                    count++;
                    break;
                }
                default:
                    break;
            }
        } else {
            if (*pFormat != *pStr) {
                break;
            }
            pFormat++;
            pStr++;
        }
    }
    va_end(args);
    return count;
}

int main() {
    char str[] = "Name: John, Number: 123";
    char name[20];
    int num;
    int result = mySscanf(str, "Name: %s, Number: %d", name, &num);
    if (result == 2) {
        printf("Name: %s, Number: %d\n", name, num);
    } else {
        printf("Parsing failed\n");
    }
    return 0;
}

mySscanf函数中,利用指针pStr遍历源字符串strpFormat遍历格式化字符串format。通过va_list处理可变参数。当遇到'%'时,根据后续字符判断解析类型,如's'解析字符串,'d'解析整数,通过指针操作从源字符串中读取数据并存储到相应变量中,实现自定义的字符串解析功能,并返回成功解析的参数个数。

通过以上对C语言中指针操作字符串函数的深入探讨,包括标准库函数、自定义函数、复杂操作以及格式化与解析等方面的应用,我们可以更灵活高效地处理字符串相关的编程任务,同时也加深了对指针在C语言中重要作用的理解。在实际编程中,要注意指针操作的安全性和正确性,避免出现内存泄漏、指针越界等问题。