C语言字符串知识

一、什么是字符串?

字符串是一个多个字符的序列(包括:数字、字母、空格等)如:
“This is an array of strings” 要注意:双引号不是字符串的一部分。双引号仅告诉编译器它括起来的是字符串

二、如何定义字符串

C语言没有专门用于存储字符串的变量类型(java中 String 这种),字符串都被存储在char类型的数组中。数组都由连续存储单元组成,字符串中的字符被存储在相邻的存储单元中,每个存储单元(一字节)存储一个字符。

​ 在 C 语言中,字符串通常是以字符数组的形式存在的。 我们可以通过直接赋值或使用特定的函数来初始化字符串。

1、字符串常量

双引号括起来的内容称为字符串常量(C语言中不存在字符串变量)。
双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串存储在内存中

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

// 在C语言中,字符串字面量(如 "this is test")通常被存储在程序的只读数据段中,
// 并且每次程序中使用相同的字符串字面量时,它们会共享同一个内存地址。
// 而当你将字符串字面量赋值给一个字符数组(如 char arr[] = "this is test";)时,
// 数组 arr 会在栈上分配空间,并将字符串的副本存储在那里。

	char arr[] = "this is test";
	printf("%p \n%p","this is test",arr);

//	第一个% p 打印的是字符串字面量 "this is test" 的地址,这是在程序的只读数据段中的地址。
//	第二个 % p 打印的是数组 arr 的地址,这是在栈上的地址。
//	这里的地址表示的是数组的首地址

}

运行结果:

image-20241106192113136

2、字符数组

字符数组: 存放字符数据的数组

初始化字符数组:

  1. 我们在定义字符串数组时,如果在程序运行前知道要存储内容,就可以开辟足够大的空间用来存储字符串,或是省略数组初始化声明中的大小(编译器会自动计算数组大小,并开辟出足够大的空间)。

    // 初始化字符数组
    //	编译器会自动计算数组大小,并开辟出足够大的空间
    char arr[]    = "this is test";
    char arr2[15] = "this is test";
    // 在开辟空间大于字符串所需空间时,空间剩余部分会被初始化为‘\0’,测试看一下
    
    printf("%s\n", arr);
    printf("%s", arr2);
    

arr【2】测试结果:

image-20241106195313072

  1. 不常用的初始化
char arr1[] = {'A','B'};
char arr2[] = {'A','B','\0'};

printf("%s\n", arr1);  // AB烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫AB
printf("%s", arr2);    // AB

3.字符串数组

字符串数组是这样的数组,它的每个元素又都是一个字符串。字符串数组是二维数组。采用二维字符串数组时,可先将二维变成若干个一维数组,其处理方法与一维数组相同。例如:

char s[2][10];
  //  该语句定义s是一个具有两个字符串元素的字符串数组,每个字符串元素的长度为10个字符,包括字符串结束符“\0”。
//    一维字符串数组元素的引用,使用的是第一个下标元素,如s[0]表示数组中第一个字符串元素的首地址,s[1]表示数组中第二个字符串元素的首地址。
//    可以使用以下几种方法对字符串数组进行赋值操作。
    
char s[2][10];
char name[2][8] = {"Mary","Smith"};  // name[0] = "Mary", name[1] = "Smith"; 
char name[2][8] = { {'M','a','r','y'},{'S','m','i','t','h'}}  // 如果定义为name[2][5],就会没有 '\0'

printf("%s \n%s", name[0], name2[1]);

4.字符串中常用函数

sizeof关键字,获取某个数据类型所占用空间的字节数。

strlen为c语言中<string.h>的库函数,要计算长度的字符串。计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。

eg:

char arr[] = { 'a','b','\0','a','b' };
printf("%d", strlen(arr));		   //答案为: 2

程序例:

#include <stdio.h>
int main(){
char str[20] = "Hello";
int array_size = sizeof(str);  // 返回 20,即数组的总大小
int string_length = strlen(str);  // 返回 5,即有效字符的数量

printf("array_size:%d \nstring_length:%d", array_size, string_length);   // 打印结果
}
函数名: strcpy

功 能: 将参数src字符串拷贝至参数dest所指的地址
用 法: char *strcpy(char *dest, const char *src);
返回值: 返回参数dest的字符串起始地址
说 明: 如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况,在编写程序时需特别留意,或者用strncpy()来取代;
程序例:

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

int main(void)
 {
    char string[10];
    char str1[] = "abcdefghi";

    strcpy(string, str1);
    printf("%s\n", string);  // 输出:abcdefghi
    return 0;

 }
函数名: strncpy

功 能: 将字符串src前n个字符拷贝到字符串dest
用 法: char *strncpy(char *dest, const char *src, size_t n);
返回值: 返回参数dest的字符串起始地址
说 明: 不像strcpy(),strncpy()不会向dest追加结束标记’\0’;src和dest所指的内存区域不能重叠,且dest必须有足够的空间放置n个字符;
程序例:

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

int main(void)
{
   char string[10];
   char *str1 = "abcdefghi";

   strncpy(string, str1, 3);
   string[3] = '\0';
   printf("%s\n", string);  // 输出:abc
   return 0;
}
函数名: strcat

功 能: 字符串拼接函数
用 法: char *strcat(char *dest, const char *src);
返回值: 返回dest字符串起始地址
说 明: strcat() 会将参数src字符串复制到参数dest所指的字符串尾部;
dest最后的结束字符’\0’会被覆盖掉,并在连接后的字符串的尾部再增加一个’\0’;
dest与src所指的内存空间不能重叠,且dest要有足够的空间来容纳要复制的字符串;
程序例:

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

int main(void)
{
   char destination[25];
   char *blank = " ", *c = "C++", *Borland = "Borland";

   strcpy(destination, Borland);
   strcat(destination, blank);
   strcat(destination, c);

   printf("%s\n", destination);  // 输出:Borland C++
   return 0;
}
函数名: strncat

功 能: 将n个字符追加到字符串的结尾
用 法: char *strncat(char *dest, const char *src, size_t n);
返回值: 返回dest字符串起始地址
说 明: strncat()将会从字符串src的开头拷贝n个字符到dest字符串尾部,dest要有足够的空间来容纳要拷贝的字符串;
如果n大于字符串src的长度,那么仅将src全部追加到dest的尾部;
strncat()会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’;
程序例:

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

int main()
{
    char arr[100] = "hello world\n";
    char arr2[30] = "你好,大学";
    strncat(arr, arr2, 1000);  
    printf("%s\n", arr); 
    return  0;
}

image-20241106204640659

函数名: strcmp

功 能: 字符串比较
用 法: int strcmp(const char *s1, const char *s2);
返回值: 根据ASCII码比较,若参数s1和s2字符串相同则返回0,s1若大于s2则返回大于0的值,s1若小于s2则返回小于0的值
程序例:

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

int main(void)
 {
    char a[] = "aBcDeF";
    char b[] = "AbCdEf";
	char c[] = "aacdef";
 	char d[] = "aBcDeF";
	printf("strcmp(a, b) : %d\n", strcmp(a, b));  // 输出:1
    printf("strcmp(a, c) : %d\n", strcmp(a, c));  // 输出:-1
	printf("strcmp(a, d) : %d\n", strcmp(a, d));  // 输出:0
    return 0;
 }
<think>嗯,用户问的是为什么在读取文件时某些字符串会显示为负值。我之前已经回答过这个问题,现在用户再次提问,可能希望得到更详细的分析,或者之前的回答中有需要补充的地方。 首先,我需要回顾之前的回答,确保这次的内容不重复且更全面。之前的回答提到了有符号字符类型、编码格式不匹配、数据截断和缓冲区溢出这几个原因,并给出了解决方案和示例代码。现在用户提供的引用资料中有关于C语言文件操作、Linux的read函数、设备树属性读取以及二进制文件处理的内容,需要结合这些引用进一步分析。 用户提供的引用1提到了of_property_read_string_array函数,这属于设备树属性读取,可能和内核模块开发相关。但用户的问题是关于文件读取中的负数字符串,所以这个引用可能关联到如何正确读取字符串数组,但暂时可能不直接相关。不过,可以注意返回值失败时返回负值,这可能与错误处理有关,但用户的问题是关于字符串内容为负,而不是函数返回值。 引用2和3提到的是read()函数在读取文件时的情况,特别是read()不会自动添加字符串结束符'\0',并且会包含换行符。这可能导致在后续处理字符串时,如果没有正确处理结束符,可能会读取到超出缓冲区的内存内容,其中可能包含负值。比如,如果缓冲区未初始化,残留的数据中有符号字符可能显示为负值。 引用4和5涉及C语言的fread函数和文件结束标志EOF。特别是,引用5指出EOF在文本文件中是-1,而二进制文件中可能存在值为-1的字节,导致误判结束。这可能关联到用户的问题,如果读取二进制文件时,某些字节被当作有符号字符,其值可能为负,比如0xFF会被解释为-1,而如果错误地以文本方式处理二进制文件,就会出现问题。 现在用户的问题核心是字符串显示为负值,结合之前的回答和引用,需要更详细地分析各可能原因,并引用提供的资料来支持。 首先,考虑有符号字符类型的问题。在C语言中,char类型默认是有符号的,范围是-128到127。当读取的字节值超过127时,会被解释为负数。例如,0xFE会被视为-2。这在处理二进制数据或非ASCII字符时常见。解决方案是使用unsigned char类型,如之前的建议。 然后,编码问题。比如UTF-8编码中的多字节字符,如汉字,每个字节可能超过0x7F,导致被解释为负数。引用3中的Python示例显示读取的内容包含换行符,但未提到负值,可能因为Python中字符串处理自动以Unicode处理,而C语言中处理字节需要更谨慎。 数据截断和未初始化的缓冲区也可能导致问题。引用2指出read()不会自动添加'\0',如果用户将缓冲区当作C字符串处理(即以'\0'结尾),但未手动添加终止符,可能会导致读取到缓冲区后面的内存内容,其中可能包含随机数据,包括负值。例如,如果定义char buffer[100],用read()读取50字节后,未设置buffer[50] = '\0',那么用printf("%s", buffer)会继续读取后面的内存,直到遇到'\0',而这些内存可能包含负值的字节。 另外,文件打开方式是否正确。例如,以文本模式打开二进制文件,在某些系统(如Windows)中,可能会进行换行符转换(如将\r\n转换为\n),导致字节值被修改,或者读取到意外的字节。而引用5提到EOF的处理,如果在二进制文件中读取到0xFF(即-1),可能被误认为是EOF,但用户的问题是关于字符串中的负值,而不是文件结束的判断错误,不过这可能关联到读取过程中是否正确处理了所有字节。 解决方案方面,需要强调使用正确的数据类型(unsigned char),正确处理字符串终止符,以及文件打开模式。例如,在C语言中,使用fread读取二进制文件时,应使用"rb"模式,避免文本模式的转换。同时,处理多字节编码时,需要正确解析字节序列,而不是单独处理每个字节为字符。 此外,用户可能混淆了字节值和字符显示。例如,在调试时直接打印char类型的十进制值,而没有转换为无符号类型,导致显示负数。如引用5中的EOF是-1,但实际文件中可能有0xFF字节,作为有符号char就是-1,这可能被误认为是EOF,但其实只是普通数据。 总结,需要从数据类型、编码、文件处理方式、缓冲区初始化等方面详细说明原因,并结合用户提供的引用资料中的内容,如read()函数的行为、字符串终止符的重要性、二进制与文本模式的区别等,来支持分析。</think>在文件读取过程中,字符串中的某些字符显示为负值,主要与**数据存储方式、字符编码解析和编程语言特性**密切相关。以下是结合技术细节的深度分析: --- ### 一、核心原因剖析 #### 1. **有符号字符类型的数值范围限制** - **底层机制**:在C语言中,`char`类型默认为有符号(取值范围$-128 \sim 127$),当读取的字节值超过$0x7F$(十进制127)时,会被解释为负值。例如: - 字节值$0xFE \rightarrow$ 二进制补码表示为$-2$[^5] - 汉字编码字节$0xB8 \rightarrow$ 十进制$-72$(UTF-8编码中的常见情况) - **验证方法**:直接打印`char`类型数值会显示负数,需强制转换为无符号类型: ```c printf("%d → %u", c, (unsigned char)c); // 输出:-72 → 184 ``` #### 2. **编码格式与字节流解析** - **多字节编码问题**:UTF-8、GBK等编码中,单个字符由多个字节组成(如汉字"中"的UTF-8编码为`0xE4 0xB8 0xAD`)。若逐字节解析为有符号字符,则每个字节分别显示为$-28$, $-72$, $-83$[^3]。 - **文本与二进制模式混淆**: - **文本模式**:Windows系统中,以`"r"`模式打开文件时,换行符`\r\n`会被转换为`\n`,可能破坏原始字节序列。 - **二进制模式**:必须使用`"rb"`模式读取原始字节,避免自动转换[^4]。 #### 3. **缓冲区处理不当** - **未终止的字符串**:`read()`函数不会自动添加字符串终止符`\0`。若缓冲区未初始化或未手动添加`\0`,后续操作可能读取到无效内存数据(包含负值)[^2]: ```c char buf[64]; read(fd, buf, 64); // 危险:buf可能未以\0结尾 printf("%s", buf); // 可能越界读取到负值字节 ``` - **内存残留数据**:未初始化的缓冲区可能包含历史数据,如: ```c char buf[64]; // 未初始化,残留数据可能含负值 ``` --- ### 二、解决方案与最佳实践 #### 1. **规范数据类型与输出** - **统一使用无符号字符**: ```c unsigned char buffer[1024]; // 明确声明无符号类型 fread(buffer, 1, sizeof(buffer), fp); ``` - **安全输出字节值**: ```c for (int i=0; i<len; i++) { printf("%u ", buffer[i]); // 强制以无符号格式输出 } ``` #### 2. **正确处理编码与文件模式** - **编码转换**:对多字节编码(如UTF-8),需使用专用库(如`iconv`)解析完整字符,而非逐字节处理。 - **严格区分文件模式**: ```c // 文本文件(自动处理换行符) FILE *ftxt = fopen("file.txt", "r"); // 二进制文件(原始字节操作) FILE *fbin = fopen("data.bin", "rb"); ``` #### 3. **内存管理规范化** - **初始化缓冲区**: ```c char buf[1024] = {0}; // 初始化为全零,避免残留数据 ``` - **显式添加终止符**: ```c ssize_t bytes = read(fd, buf, sizeof(buf)-1); buf[bytes] = '\0'; // 确保字符串正确终止 ``` --- ### 三、典型错误案例分析 #### 错误代码示例 ```c FILE *fp = fopen("data.bin", "r"); // 错误:未使用二进制模式 char c; while (fread(&c, 1, 1, fp)) { printf("%d ", c); // 错误:输出有符号char值 } ``` #### 修正方案 ```c FILE *fp = fopen("data.bin", "rb"); // 正确:二进制模式 unsigned char c; while (fread(&c, 1, 1, fp)) { printf("%u ", c); // 正确:输出无符号值 } ``` --- ### 四、扩展讨论 #### 1. **为什么Python等高级语言较少出现此问题?** - **自动类型处理**:Python的`bytes`类型直接存储原始字节(范围$0 \sim 255$),而字符串(`str`)会自动解码为Unicode,避免直接暴露字节值[^3]。 #### 2. **二进制文件与文本文件的本质区别** - **数据存储**:二进制文件直接存储内存原始字节,可能包含任何值(包括$0x00 \sim 0xFF$)。 - **EOF处理**:文本文件依赖`EOF`标记($-1$),而二进制文件需通过`feof()`判断结束[^5]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值