掘根宝典之C语言字符,字符串常量,字符串数组,字符指针,字符指针与字符串数组的区别

字符

什么是字符?

字符就是我们键盘上面能打出的字符,包括所有英语大小写单词,数字0到9,以及各种符号等。

C语言中的字符是用单引号括起来的,例如'a', 'b', 'A', '1'等。字符可以用来表示字母、数字、符号等。在C语言中,字符类型被定义为一个字节大小的整数。

字符串

用双引号括起来的就是字符串字面量,也叫字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串存在内存中。

”I an a string in an array."
"Something i poinced at me,"
"Hete s some strings:"

都是字符串字面量。
从ANSIC标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为申联起物字符串字面量。例如:

char greeting[50]="Hello, and"" how are" " you"
" today!";


与下面的代码等价:

char greeting[50]="Hello, and how are you today!";


如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠(\):

printf("\"Run, Spot, run!\" exclaimed Dick.\n");


输出如下:

"Run, Spot, run!" exclaimed Dick.


字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。

用双引号括起来的内容被视为指向该字符串储存位置的指针。这类似于把数组名作为指向该数组位置的指针。我们看个例子

#include<stdio.h>
int main()
{
	printf("%s  %p  %c", "we", "are", *"friend");
}

结果是

we  00007FF6276F9C2C  f

这表明"are"代表一个地址。*"friend"表示该字符串所指地址上存储的值,应该是字符串*“friend"的首字符

这表明下面这个语句

conat char*a="abcd";

a被赋予的是字符串首字符的地址 

字符串数组

什么是字符串数组?什么是字符数组?

在C语言中,字符数组和字符串数组的区别在于如何使用和表示数据。

  1. 字符数组:字符数组是一个存储字符的固定长度的数组。它可以存储单个字符或一串字符,但没有自动添加字符串结束符'\0'。字符数组可以用来存储和处理任意字节的数据,不仅仅限于存储字符串。例如,可以定义一个字符数组来存储用户的输入,或者用于存储二进制数据。

示例:

char arr[10]; // 定义一个长度为10的字符数组
arr[0] = 'H';
arr[1] = 'e';
arr[2] = 'l';
arr[3] = 'l';
arr[4] = 'o';

  1. 字符串数组:字符串数组是一个数组,其中每个元素都是一个字符串。字符串是以字符数组的形式呈现,但以'\0'(空字符)作为结束符,表示字符串的结束。字符串数组可以用于存储和处理一系列的文本数据。

示例:

char str[10] = "Hello"; // 定义一个长度为10的字符串数组

总结: 字符数组和字符串数组的区别在于是否有字符串结束符。字符数组可以存储任意字节的数据,而字符串数组是一系列以'\0'结尾的字符数组。在处理字符串时,我们通常使用字符串数组来表示和操作文本数据。

字符串数组

定义字符串数组时,必须让编译器知道需要多少空间。一种方法是用足够空间的数组存储字符串,在下面的声明中,用指定的字符串初始化数组m1:

const char m1 [20]="hello world";

const表明不会更改这个字符串。
这种形式的初始化比标准的数组初始化形式简单得多:

const char m1[20]={'h','e'.'l','l','o',' ','w','o','r','l','d','\0'}


注意最后的空字符。没有这个空字符,这就不是一个字符串,而是一个字符数组。

在指定数组大小时,要确保数组的元素个数至少比字符串长度多1(为了容纳空字符)。所有未被使元素都被自动初始化为\0(空字符),如下图所示




通常,让编译器确定数组的大小很方便。回忆一下,省略数组初始化声明中的大小,编译器会自动计算数组的大小:

const char m2[]="If you can't think of anything, fake it.";

让编译器确定初始化字符数组的大小很合理。因为处理字符串的函数通常都不知道数组的大小,这些系数通过查找字符串末尾的空字符确定字符串在何处结束。
让编译器计算数组的大小只能用在初始化数组时。如果创建一个稍后再填充的数组,就必须在声明时能大杯。声明数组时,数组大小必须是可求值的整数。在C99新增变长数组之前,数组的大小必须是整型常量,包括由整型常量组成的表达式。

int n = 8;
// 有效
char cakes (2 + 5];//有效,数组大小是整型常量表达式
char cookies[1];
char pies [2+sizeof(long double)+1];//有效
char c[n];//在C99标准之前无效,C99标准之后这种数组是变长数组

字符串数组名的意义

字符数组名和其他数组名一样,是该数组首元素的地址。

因此,假设有下面的初始化:

char car(10]="Tata";


那么,以下表达式都为真:

car==&car[0];
*car=='T';
*(cat+1)==car[1]=='a';


还可以使用指针表示法创建字符串。例如,程序清单11中使用了下面的声明,

const char * ptl="Something is pointing at me.";

这声明和下面的声明几乎是相等的

const char ar1[]="Something is pointing at me.";

那它们之间有什么区别呢?我们马上揭晓

数组和指针

数组形式和指针形式有何不同?

数组形式

以上面的声明为例,数组形式(ar1[])在计算机的内存中分配为一个内含29个元素的数组(每个元素对应一个字符,还加上一个末尾的空字符,\0)每个元素被初始化为字符串字面量对应的字符。

通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序数入两时,也载入了程序中的字符串

字符串储存在静态存储区(suatic memory)中。但是,程序在开始运行时才会为数组分配内存。此时,才将字符串拷贝到教组中(我之后会详细讲解)。注意,此时字符串有两副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数组中的字符串。

此后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别名。这里关键要理解,在数组形式中,ar1是地址常量。

不能更改ar1,如果改变了ar1,则意味着改变了数组的存储位置(即地址)。可以进行类似ar1+1这样的操作,标识数组的下一个元素。

但是不允许进行++ar1这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。
 

指针形式

指针形式(*pt1)也使得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为指针变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。

该变量最初指向该字符串的首字符,但是它的值可以改变。因此,可以使用递增运算符。例如,++pt1将指向第2个字符(o)。

字符串字面量被视为const数据。由于pt1指向这个const数据,所以应该把pt1声明为指向const据的指针。

这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即,pt1指向的位置)。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为const。

总之

初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。

我们可以来验证一下

#define MSG "I'm special"
#include <stdio.h>
int main()
{
char ar[] = MSG;
const char* pt = MSG;
printf("address of \"I'm special\": %p \n", "I'm special"); 
printf("            address ar: %p\n", ar);
printf("           address pt : %p\n", pt);
printf("         address of MSG:%p\n", MSG);
printf("address of \"I'm special\": %p \n", "I'm special");
return 0;
}

结果


该程序的输出说明了什么?

第一,pt和MSG的地址相同,而ax的地址不同,这与我们前面讨论的内答一致,

第二,虽然字符串字面量"I'm areclal”在程序的两个printf()函数中出现了两次,但是编译器只使用了一个存储位置,而且与MSG的地址相同。编译器可以把多次使用的相同字面量存在一处或生地,另一个编译器可能在不同的位置储存3个"1'mspecial”。

第三,静态数据使用的内存与a正使用的动态内存不同。不仅值不同,特定编译器甚至使用不同的位数表示两种内存。

我们再看一个例子

#include<stdio.h>
#include<string.h>
int main()
{
	const char* p1 = "abcdef";

	printf("%zd\n", sizeof(p1));
	printf("%zd\n", sizeof(*p1));
	printf("%zd", strlen(p1));
}

结果

8
1
6

 这表明,sizeof(p1)是这个指针占的空间,sizeof(*p1)是p1指向的第一个字符所占的空间,strlen(p1)是指针指向字符串的长度

数组和指针表示字符串的区别是否很重要?通常不太重要,但是这取决于想用程序做什么。我们来进一步讨论这个主题。

字符串数组和字符指针的区别

初始化字符数组来存储字符串和初始化指针来指向字符串有何区别(“指向字符串”的意思是指向字符的首字符)?例如,假设有下面两个声明。

const char *head="I love Tillie!":
char heart[] ="I love Millie!";


两者主要的区别是:数组名heart是常量,而指针名head是变量。

那么,实际使用有什么区别?

首先,两者都可以使用数组表示法:

for (i=0;i< 6;i++)
putchar (heart[i]);

putchar('\n');

for (i=0;i< 6;1++)
putchar(head[i]);

putchar('\n');


上面两段代码的输出是:

I love
I love

其次,两者都能进行指针加法操作:

for (i= 0;i < 6; i++)
putchar(*(heart + i));

putchar('\n');

for (i= 0; i < 6; i++)
putchar(*(head + i));

putchar('\n');


输出如下:

I love
I love


但是,只有指针表示法可以进行递增操作:

while (*(head) !=\0') /*在字符串末尾处停止*/
putchar(*(head++)); /*打印字符,指针指向下一个位置*/


这段代码的输出如下:

I love Millie!


假设想让head和heart统一,可以这样做:

head = heart; /* head现在指向数组heart */


这使得head指针指向heart数组的首元素。
但是,不能这样做:

heart = head; /*非法构造,不能这样写 */


类似于x=3和3=x的情况,赚值运算符的左侧必须是变量(或概括地说是可修改的左值,如说*pt_int)。顺带一提,head=heart,不会导致head指向的字符串消失,这样做只是改变了储存在head中的地址,除非已经保存了"I love Millie!"的地址,否则当 head指向别处时,就无法再访问该字符串

另外,还可以改变heart数组中元素的信息:

heart[7]='M';或者*(heart+7)= 'M';


数组的元素是变量(除非数组被声明为const),但是数组名不是变量。

我们可以再看一个例子

#include<stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	printf("arr1:%p\n", arr1);
	printf("arr2:%p\n\n", arr2);
	printf("p1:%p\n", p1);
	printf("p2:%p\n\n", p2);
	printf("&p1:%p\n", &p1);
	printf("&p2:%p\n\n", &p2);
}

结果

未使用const限定符的指针

我们来看一下未使用const限定符的指针初始化:

char * word ="frame";


是否能使用该指针修改这个字符串?

word[1]='1';// 是否允许?


编译器可能允许这样做(VS2022不允许这么做),但是对当前的C标准而言,这样的行为是未定义的。

例如,这样的语句可能导致内存访问错误。原因前面提到过,编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。例如,下面的语句都引用字符串“Klingon”的一个内存位置:

char * pl="Klingon";
P1[0]='F';// ok?
printf("Klingon");
printf(": Beware the &ss!\n", "Klingon");


也就是说,编译器可以用相同的地址替换每个“Klingon”实例。如果编译器使用这种单次副本表示法,并允许p1[0]修改'F',那将影响所有使用该字符串的代码。

所以以上语句打印字符串字面量"Klingon时实际上显示的是"Flingon":

Flingon: Beware the Flingons!


实际上在过去,一些编译器由于这方面的原因,其行为难以捉摸,而另一些编译器则导致程序异常中断。因此,建议在把指针初始化为字符串字面量时使用const限定符:

const char *pl="Klingon"; //推荐用法


然而,把非 const 数组初始化为字符串字面量却不会导致类似的问题。因为数组获得的是原始字符串的副本。
 

总之,如果打算修改字符串,就不要用指针指向字符串字面量。

字符串二维数组和指针的区别

我们看个例子

#include<stdio.h>
#define SLEN 40
#define LIM 5
int main()
{
	const char* mytalents[LIM] = {// 表示字符串
	"Adding numbers swiftly",
		"Multiplying accurately", "Stashing data",
		"Following instructionstothe letter",
		"Understanding the C language"
	};
	char yourtalents[LIM][SLEN] = {
		"Walking in a straight line",
		"Sleeping", "Watching television",
		"Mailing letters", "Reading email"
	};
			int i;
	puts("Let's compare talents.");
	printf("%-36s %-25s\n","My Talents", "Your Talents");
	for (i = 0; i < LIM; i++)
		printf("%-36s %-25s\n", mytalents[i], yourtalents[i]);
	printf("\nsizeof mytalents: %zd, sizeof yourtalents: %zd\n",
		sizeof(mytalents), sizeof(yourtalents));
	return 0;
}

结果是 

从某些方面来看,mytalents和yourtalents非常相似。两者都代表5个字符串。使用一个下标时都分别表示一个字符串,如mytalents[0]和yourtalents[0];使用两个下标时都分别表示一个字符,例如 mytalents[1][2]表示 mytalents 数组中第2个指针所指向的字符串的第3个字符‘l’,yourtalents[1][2]表示youttalentes数组的第2个字符串的第3个字符'e'。而且,两者的初始化方式也相同。

但是,它们也有区别。mytalents数组是一个内含5个指针的数组,在我们的系统中共占用40字节。而yourtalents是一个内含5个数组的数组,每个数组内含40个char类型的值,共占用200字节。所以,虽然mytalents[0]和yourtalents [0]都分别表示一个字符串,但mytalents和yourtalents的类型并不相同。

mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符串字面量被储存在静态内存中;而yourtalents中的数组则储存着字符串字面量的副本,所以每个字符串都被储存了两次。此外,为字符串数组分配内存的使用率较低。yourtalents中的每个元素的大小必须相同,而且必须是能储存最长字符串的大小。
 

我们可以把yourtalents想象成矩形二维数组,每行的长度都是40字节;把mytalents想象成不规则的数组,每行的长度不同。

下图演示了这两种数组的情况(实际上,mytalents数组的指针元素所指向的字符串不必储存在连续的内存中,图中所示只是为了强调两种数组的不同)。

综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。

但是,指针数组也有自身的缺点。mytalents中的指针指向的字符串字面量不能更改;而yourtalentsde 中的内容可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/417212.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

继续预训练对大语言模型的影响

翻译自文章&#xff1a;Investigating Continual Pretraining in Large Language Models: Insights and Implications 摘要 本文研究了大型语言模型&#xff08;LLMs&#xff09;中不断学习&#xff08;CL&#xff09;的不断发展领域&#xff0c;重点是制定有效和可持续的训练…

【前端素材】推荐优质在线服饰购物电商网页Uthr平台模板(附源码)

一、需求分析 1、系统定义 在线服饰购物商城是指一个通过互联网提供服装和配饰购买服务的电子商务平台。这类商城通常提供一个网站或移动应用程序&#xff0c;让顾客可以浏览、选择和购买各种类型的服装、鞋帽、包包、配饰等时尚商品。 2、功能需求 在线服饰购物商城是指一…

网工内推 | 项目经理,软考证书优先,最高26K,加班补贴

01 龙盈智达 招聘岗位&#xff1a;项目经理 职责描述&#xff1a; 1 根据业务员需求&#xff0c;完成生态圈下账簿中心系统的开发管理工作。 2 负责账簿中心实施过程中的需求调研分析、方案设计、开发测试、系统上线等工作的计划、组织协调、沟通等方面管理工作。 3 完成系统核…

计算机网络-网络互连和互联网(四)

1.TCP协议&#xff1a; 传输控制协议&#xff0c;面向字节流按顺序连接&#xff0c;可靠&#xff0c;全双工&#xff0c;可变滑动窗口&#xff0c;缓冲累积传送。协议号为6。下面是TCP段&#xff08;段头&#xff09;&#xff0c;TCP头&#xff08;传输头&#xff09;&#xf…

【科研基础】小波变换

[1].参考开源库1&#xff1a;https://github.com/fbcotter/pytorch_wavelets Cotter, Fergal. Uses of complex wavelets in deep convolutional neural networks. Diss. 2020. [2].参考开源库2&#xff1a;https://github.com/brunobelloni/wran-sr-pytorch [3] F:\A\1.Code\3…

【GameFramework框架内置模块】7、事件(Event)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…

java工具类之解析地址

输出 代码实现 import java.util.regex.Matcher; import java.util.regex.Pattern;public class AddressResolutionUtil {/*** 解析地址* author ys* param address* return*/public static String addressResolution(String address){String regex"(?<province>…

Keepalived 双机热备基础知识

7.1 Keepalived 双机热备基础知识 Keepalived起初是专门针对LVS设计的一款强大的辅助工具&#xff0c;主要用来提供故障切换(Failover) 和健康检查査(Health Checking)功能一一判断LVS 负载调度器、节点服务器的可用性&#xff0c;及时隔离并替 换为新的服务器&#xff0c;当故…

从零开始学习Netty - 学习笔记 -Netty入门【ByteBuf】

5.2.4.ByteBuf ByteBuf 是 Netty 框架中用于处理字节数据的一个核心类。它提供了一个灵活而高效的方式来处理字节数据&#xff0c;适用于网络编程和其他需要处理字节数据的场景。 ByteBuf的特点 可扩展性&#xff1a; ByteBuf 支持动态扩容&#xff0c;可以根据需要自动增长容…

1978-2022年各省农业总产值数据(无缺失)

1978-2022年各省农业总产值数据(无缺失) 1、时间&#xff1a;1978-2022年 2、范围&#xff1a;包括全国31省 3、来源&#xff1a;国家统计J、统计NJ、各省NJ 4、指标解释&#xff1a;农业总产值是一定时期&#xff08;通常为一年&#xff09;内以货币形式表现的农、林、牧、…

【C#】忽略大小的替换字符串

结果 代码 string a"aAAAAAAaBBaaCC"; string b Strings.Replace(a, "a", "D", 1, -1, CompareMethod.Text);

学习大语言模型(LLM),从这里开始

在见识了ChatGPT的各种强大能力后&#xff0c;不少 NLP一线从业人员很自然地想到&#xff0c;以后开发者只要借助 ChatGPT&#xff0c;就可以做到现在大部分NLP工程师在做的事&#xff0c;比如文本分类、实体抽取、文本推理等。甚至随着大语言模型&#xff08;largelanguagemod…

面试数据库篇(mysql)- 07索引创建原则与失效及优化

索引创建原则 1). 针对于数据量较大,且查询比较频繁的表建立索引。 2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。 3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。 4). 如果是字符…

算法基础(三)(模拟)

1.模拟算法介绍&#xff1a; 模拟算法通过模拟实际情况来解决问题&#xff0c;一般容易理解但是实现起来比较复杂&#xff0c;有很多需要注意的细节&#xff0c;或者是一些所谓很“麻烦”的东西。模拟题一般不涉及太难的算法&#xff0c;一般就是由较多的简单但是不好处理的部…

buuctf_web_knife

题目&#xff1a; 哥们&#xff0c;名字叫白给的shell&#xff0c;嘻嘻。本题主要练习一下蚁剑工具 激动的心&#xff0c;颤抖的手&#xff0c;我打开了蚁剑。在空白处右击 然后&#xff0c;输入URL&#xff0c;密码&#xff08;所以上面说白给&#xff09;是Syc 添加后点进…

【Java程序设计】【C00322】基于Springboot的高校竞赛管理系统(有论文)

基于Springboot的高校竞赛管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的高校竞赛管理系统&#xff0c;本系统有管理员、老师、专家以及用户四种角色&#xff1b; 管理员&#xff1a;首页、个人中心、管…

1 数据分析概述与职业操守

1、 EDIT数字化模型 E——exploration探索 &#xff08;是什么&#xff09; 业务运行探索&#xff1a;探索关注企业各项业务的运行状态、各项指标是否合规以及各项业务的具体数据情况等。 指标体系——目标&#xff08;O&#xff09;、策略&#xff08;S&#xff09;、指标&a…

jenkins插件下载失败bug

如果遇到安装jenkins插件经常失败并报以下类似错误&#xff0c;很可能是因为jenkins国外官方插件地址下载速度非常慢&#xff0c;我们可以修改为国内插件地址。 java.io.IOException: Failed to load: SCM API Plugin (scm-api 676.v886669a_199a_a_) - Jenkins (2.361.4) or h…

cmake如何将源文件按照指定的组织方式进行分组,在IDE例如vistual stdio或者xcode项目工程文件中展示和管理这些源文件

1、我们先来看下面的一个项目的结构 TestGroup工程下面的main.cpp同级的有两个文件夹&#xff0c;到时候我们想在IDE比如xcode或者visual stdio2019中也显示这样的结构怎么弄呢(答案是用cmake中的source_group函数) 2、假如没有用source_group生成的xcode工程是怎么样的&#…

map和set的简单介绍

由于博主的能力有限&#xff0c;所以为了方便大家对于map和set的学习&#xff0c;我放一个官方的map和set的链接供大家参考&#xff1a; https://cplusplus.com/ 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque&#x…