C语言学习----字符串数组和字符串指针

🌈在C Primer Plus 第六版中第11章的字符串和字符串函数讲的很好~ 对于字符串和字符串指针的用法又更深入了解了一些~

🐬 本blog为 C Primer Plus 的记录~
☘️对于字符串指针和数组更加深入了解~
🌺省流:1️⃣字符串常量被储存在静态内存中;2️⃣字符串数组则储存着字符串字面量的副本,相当于拷贝~ 所以每个字符串都被储存了两次,指针则指向了字符串的地址。3️⃣字符串数组名是一个常量
🍭加油加油~ (●ˇ∀ˇ●)

字符串数组和字符串指针

文章目录

  • 字符串数组和字符串指针
    • 在程序中定义字符串
      • 字符串字面量(字符串常量)
      • 字符串数组和初始化
      • 数组和指针
      • 数组和指针的区别
        • 未使用const限定符的指针初始化
      • 字符串数组

字符串是以空字符(\0)结尾的char类型数组。

puts();

和printf()函数一样,puts()函数也属于stdio.h系列的输入/输出函数。但是,与printf()不同的是,puts()函数只显示字符串,而且自动在显示的字符串末尾加上换行符。

在程序中定义字符串

字符串字面量(字符串常量)

🍎用双引号括起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。

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

🍊 字符串常量属于静态存储类别(static storage class) ,这说明如果 在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在 ,即使函数被调用多次。用双引号括起来的内容被视为指向该字符串储存位置的指针。 这类似于把数组名作为指向该数组位置的指针。

字符串数组和初始化

定义字符串数组时,必须让编译器知道需要多少空间。

1️⃣一种方法是用足够空间的数组储存字符串。

在这里插入图片描述

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

(●ˇ∀ˇ●) 一般写完循环 最后结束的时候 需要赋值‘\0’ 以表示结束了。

让编译器计算数组的大小只能用在初始化数组时。如果创建一个稍后再填充的数组,就必须在声明时指定大小。声明数组时,数组大小必须是可求值的整数。

字符数组名和其他数组名一样,是该数组首元素的地址。因此,假设有下面的初始化:

char car[10] = "Tata";

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

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

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

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

该声明和下面的声明几乎相同:

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

以上两个声明表明,pt1和ar1都是该字符串的地址。在这两种情况下,带双引号的字符串本身决定了预留给字符串的存储空间。尽管如此,这两种形式并不完全相同。

数组和指针

🍶数组形式

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

通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串。**字符串储存在静态存储区(static memory)中。但是,程序在开始运行时才会为该数组分配内存。**此时,才将字符串拷贝到数组中(第 12 章将详细讲解)。注意,此时字符串有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数组中的字符串。

此后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别名。❗️ 这里关键要理解,在数组形式中,ar1是地址常量。不能更改ar1,如果改变了ar1,则意味着改变了数组的存储位置(即地址) 。可以进行类似ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。

对于上述的表述,其实就是说,数组名是一个常量,不能被修改。就和1=2一样,这样是不合规的。

如果不理解可以继续往下面看。

🌵指针形式

指针形式(*pt1)也使得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为指针变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变。 因此,可以使用递增运算符。

指针是一个变量,因此可以进行递增运算。

字符串字面量被视为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;
}

🤔想想他们输出是什么?


address of "I'm special": 0x100000f10
address ar: 0x7fff5fbff858
address pt: 0x100000f10
address of MSG: 0x100000f10
address of "I'm special": 0x100000f10

该程序的输出说明了什么?1️⃣pt和MSG的地址相同,而ar的地址不同,这与我们前面讨论的内容一致。2️⃣虽然字符串字面量"I’m special"在程序的两个 printf()函数中出现了两次,但是编译器只使用了一个存储位置 ,而且与MSG的地址相同。编译器可以把多次使用的相同字面量储存在一处或多处。 另一个编译器可能在不同的位置储存3个"I’m special"。3️⃣静态数据使用的内存与ar使用的动态内存不同。 不仅值不同,特定编译器甚至使用不同的位数表示两种内存。

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

数组和指针的区别

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

🌺数组名heart是常量,而指针名head是变量。

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

for (i = 0; i < 6; i++)putchar(heart[i]);putchar('\n');
for (i = 0; i < 6; i++)putchar(head[i]);
putchar('\n');

2️⃣其次,两者都能进行指针加法操作

for (i = 0; i < 6; i++)putchar(*(heart + i));
putchar('\n');
for (i = 0; i < 6; i++)putchar(*(head + i));
putchar('\n');

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

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

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

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

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

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

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

🌴只有指针表示法可以进行递增操作,数组的元素是变量(除非数组被声明为const),但是数组名不是变量。

未使用const限定符的指针初始化
char * word = "frame";
word[1] = 'l'; // 是否允许?

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

编译器可能允许这样做,但是对当前的C标准而言,这样的行为是未定义的。例如,这样的语句可能导致内存访问错误。原因前面提到过,编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。例如,下面的语句都引用字符串"Klingon"的一个内存位置:

char * p1 = "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数组初始化为字符串字面量却不会导致类似的问题。

因为数组获得的是原始字符串的副本。总之,如果不修改字符串,不要用指针指向字符串字面量。

也就是说,内存里面已经放了一个字符串,数组获得的是它的副本,也就是拷贝过去的。而指针是直接指向这个字符串的地址,因此指针的效率较高。

字符串数组

如果创建一个字符数组会很方便,可以通过数组下标访问多个不同的字符串。

下面来看一个🌰

const char *mytalents[LIM] = {
"Adding numbers swiftly",
"Multiplying accurately", "Stashing data",
"Following instructions to the letter",
"Understanding the C language"
};char yourtalents[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching television",
"Mailing letters", "Reading email"
};

mytalents数组是一个内含5个指针的数组,在我们的系统中共占用40字节。而yourtalents是一个内含5个数组的数组,每个数组内含40个char类型的值,共占用200字节。

虽然mytalents[0]和yourtalents[0]都分别表示一个字符串,但mytalents和yourtalents的类型并不相同。mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符串字面量被储存在静态内存中;而 yourtalents 中的数组则储存着字符串字面量的副本,所以每个字符串都被储存了两次

此外,为字符串数组分配内存的使用率较低。yourtalents 中的每个元素的大小必须相同,而且必须是能储存最长字符串的大小。我们可以把yourtalents想象成矩形二维数组,每行的长度都是40字节;把mytalents想象成不规则的数组,每行的长度不同。

☘️综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。 但是,指针数组也有自身的缺点。mytalents 中的指针指向的字符串字面量不能更改;而yourtalentsde 中的内容可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。

为何不拷贝整个字符串?假设数组有50个元素,考虑一下哪种方法更效率:拷贝一个地址还是拷贝整个数组?通常,程序要完成某项操作只需要知道地址就可以了。如果确实需要拷贝整个数组,可以使用strcpy()或strncpy()函数。

🌈ok,完结~ 如果有帮助的话,点个赞 (●ˇ∀ˇ●)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HNU-计算机网络-实验3-应用层和传输层协议分析(PacketTracer)

计算机网络 课程基础实验三应用层和传输层协议分析&#xff08;PacketTracer&#xff09; 计科210X 甘晴void 202108010XXX 【给助教的验收建议】 如果是助教&#xff0c;比起听同学读报告&#xff0c;更好的验收方式是随机抽取一个场景&#xff08;URL/HTTPS/FTP&#xff09…

Linux线程的设计

文章目录 一.理解Linux线程的本质进程地址空间是进程访问系统资源的窗口Linux系统中,线程是比进程更轻量级的执行流 二.Linux线程独立运行的原理三.基础线程控制 一.理解Linux线程的本质 进程地址空间是进程访问系统资源的窗口 Linux系统中,线程是比进程更轻量级的执行流 线程…

Mac 中文版 Navicat Premium 16 下载安装详细教程

哈喽朋友们大家好&#xff0c;今天做一期 Mac 数据库连接工具 Navicat Premium 16 的安装教程&#xff0c;很多朋友不知道怎么安装的&#xff0c;要不就是有试用期无法正常使用&#xff0c;要不就是英文的&#xff0c;改不了中文&#xff0c;大家可以跟着我的步骤安装&#xff…

RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 RabbitMQ时空之旅&#xff1a;rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用&#xff1a;如何在 RabbitMQ 中启用消息时间戳&…

视觉检测系统在半导体行业的应用

一、半导体产业链概述 半导体产业链是现代电子工业的核心组成部分&#xff0c;涵盖了从原材料到最终产品的整个生产过程。这个产业链主要分为以下几个环节&#xff1a; 1.原材料供应&#xff1a;半导体行业的基石是半导体材料&#xff0c;如硅片、化合物半导体等。这些材料需要…

挑战52天学小猪佩奇笔记--day24

52天学完小猪佩奇--day24 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day24 的视频内容总结&#xff0c;方便复习。强烈建议大家去关注一波UP&#xff0c;配合UP视频学习。 注&#xff1a;这集开始变成一段一段的猜台词&#xff0c;加…

python学习,1.变量和简单的数据类型

一、编写文章的目的 1.这是为了初学者而写的&#xff0c;学习python比较简单然后上手&#xff0c;也会过滤一些&#xff0c;如果没有提起到的&#xff0c;可以在学习的时候进行补充 2.相对来说&#xff0c;上手难度不会很难。 二、内容 1.让首字母大写&#xff1b;字母都大写…

STM32F103C8T6—烧录程序

STM32F103C8T6烧录程序方法 1. ST-Link烧录程序ST-Link软件下载ST-Link软件安装程序下载 2. 串口烧录程序CH340驱动下载安装连接程序下载 1. ST-Link烧录程序 该USB驱动程序(STSW-LINK009)适用于ST-LINK/V2, ST-LINK/V2-1和STLINK-V3板及其衍生物 首先下载ST-link驱动&#xf…

华为配置基本QinQ示例

组网需求 如图1所示&#xff0c;网络中有两个企业&#xff0c;企业1有两个分支&#xff0c;企业2有两个分支。这两个企业的各办公地的企业网都分别和运营商网络中的SwitchA和SwitchB相连&#xff0c;且公网中存在其它厂商设备&#xff0c;其外层VLAN Tag的TPID值为0x9100。 现…

解读unity内置的软阴影处理方式

解读unity内置的软阴影处理方式&#xff1a; 参考网址&#xff1a; https://blog.csdn.net/cgy56191948/article/details/105726682 https://blog.csdn.net/weixin_45776473/article/details/119582218 https://tajourney.games/5482/ 上面的博客已经论述了&#xff0c;为何出现…

5个免费、跨平台的SQLite数据库可视化工具

前言 SQLite是一个轻量级的嵌入式关系型数据库&#xff0c;目前最新的版本是 SQLite3。今天推荐5个实用的SQLite数据库可视化工具(GUI)&#xff0c;帮助大家更好的管理SQLite数据库。 什么是SQLite&#xff1f; SQLite是一个轻量级的嵌入式关系型数据库&#xff0c;它以一个…

风速预测(五)基于Pytorch的EMD-CNN-LSTM模型

目录 前言 1 风速数据EMD分解与可视化 1.1 导入数据 1.2 EMD分解 2 数据集制作与预处理 2.1 先划分数据集&#xff0c;按照8&#xff1a;2划分训练集和测试集 2.2 设置滑动窗口大小为96&#xff0c;制作数据集 3 基于Pytorch的EMD-CNN-LSTM模型预测 3.1 数据加载&…

C++软件调试与异常排查技术从入门到精通学习路线分享

目录 1、概述 2、全面了解引发C软件异常的常见原因 3、熟练掌握排查C软件异常的常见手段与方法 3.1、IDE调试 3.2、添加打印日志 3.3、分块注释代码 3.4、数据断点 3.5、历史版本比对法 3.6、Windbg静态分析与动态调试 3.7、使用IDA查看汇编代码 3.8、使用常用工具分…

2023年度佳作:AIGC、AGI、GhatGPT、人工智能大语言模型的崛起与挑战

目录 前言 01 《ChatGPT 驱动软件开发》 内容简介 02 《ChatGPT原理与实战》 内容简介 03 《神经网络与深度学习》 04 《AIGC重塑教育》 内容简介 05 《通用人工智能》 目  录 前言 2023年是人工智能大语言模型大爆发的一年&#xff0c;一些概念和英文缩写也在这一…

2020年第九届数学建模国际赛小美赛D题石头剪刀游戏与合作解题全过程文档及程序

2020年第九届数学建模国际赛小美赛 D题 石头剪刀游戏与合作 原题再现&#xff1a; 小时候你可能至少玩过几次石头剪刀游戏。在这个游戏中&#xff0c;你几乎有三个选择&#xff0c;每一个都有一个项目要打败&#xff0c;一个项目输给。石头打败剪刀&#xff0c;剪刀剪纸和布覆…

windows电脑半夜突然睡眠自动唤醒的问题查找与治理

遇见几次了&#xff0c;半夜起来上厕所&#xff0c;发现休眠的电脑居然自己开了&#xff0c;还得跑过去把电脑再休眠&#xff0c;很烦。昨天晚上居然自动唤醒两次&#xff0c;忍无可忍了&#xff0c;于是开始查找原因。 查询原因如下&#xff0c;解决方面也在后面。 固件 S3 计…

网络(九)三层路由、DHCP以及VRRP协议介绍

目录 一、三层路由 1. 定义 2. 交换原理 3. 操作演示 3.1 图示 3.2 LSW1新建vlan10、20、30&#xff0c;分别对应123接口均为access类型&#xff0c;接口4为trunkl类型&#xff0c;允许所有vlan通过 3.3 LSW2新建vlan10、20、30&#xff0c;配置接口1为trunk类型&…

一天吃透MySQL面试八股文

目录 事务的四大特性&#xff1f;数据库的三大范式事务隔离级别有哪些&#xff1f;生产环境数据库一般用的什么隔离级别呢&#xff1f;编码和字符集的关系utf8和utf8mb4的区别什么是索引&#xff1f;索引的优缺点&#xff1f;索引的作用&#xff1f;什么情况下需要建索引&…

Python 全栈体系【四阶】(六)

第四章 机器学习 五、线性模型 1. 概述 线性模型是自然界最简单的模型之一&#xff0c;它描述了一个&#xff08;或多个&#xff09;自变量对另一个因变量的影响是呈简单的比例、线性关系。例如&#xff1a; 住房每平米单价为 1 万元&#xff0c;100 平米住房价格为 100 万…

Python计算圆的面积,几何学技法大解析!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python计算圆的面积&#xff0c;几何学技法大解析&#xff0c;全文3800字&#xff0c;阅读大约15分钟。 在本文中&#xff0c;将深入探讨如何使用 Python 计算圆的面积&…