深度剖析“字符串与数组、指针”的关系

一、字符串的结构

字符串由字符组成,把一个个字符串起来就构成了字符串。

字符串的基本结构如下所示。

字符

‘H’

‘e’

‘l’

‘l’

‘o’

‘\0’

字符串

“Hello”

字符串的格式规范:

①使用时需加双引号。

②字符串最后一个字符为’\0’。它是字符串结束的标志,不在显示器上显示,但占用一个字符空间。

二、字符串的本质

在C语言中,字符串是以字符数组的形式存储的。所以,可以像处理普通数组一样处理字符串,只需要注意输入输出和字符串函数的使用。

三、字符串的定义

字符串变量的定义方法:

char s[6];

①s是变量名。

②字符串的长度为实际字符个数加1(因为要给空字符’\0’留个位置)。

四、字符串的赋值

1.数组形式:赋值

因为字符串本质上就是数组,因此它的赋值也具有数组的特点:只能在初始化时用等号。

(1)字符串的初始化

方法一:指定大小

char s[6]="Hello"; //指定字符串最大占用6个的char型内存单元

方法二:省略大小

char s[]="Hello"; //自动计算出最大占用6个的char型内存单元

(2)初始化后的赋值

和数组一样天生低人一等,给字符串变量赋值时,只有初始化时才可以使用“=”,之后均需使用strcpy()函数。

char s[10];
strcpy(s, "Hello"); //s指上一行定义的s[10]数组的首元素的内存地址。

2.指针形式:传址

字符串的本质是数组,数组的本质是占用连续内存单元的多个变量(每个内存单元大小相同,就像把一整板豆腐切成大小相同的小块,每一块可看成一个内存单元,每个内存单元存储着一个变量——数组元素)。内存是和指针密切相关的,因为指针保存的就是内存地址。因此,可以通过把字符串的首地址传给指针进行赋值,这样的指针称为“指向字符串的指针”。

举个例子:

#include <stdio.h>  
int main() {  char str[] = "Hello, World!"; // 定义一个字符数组(字符串)  char *ptr = str; // 定义一个指向字符的指针,并让它指向str的首地址  // 使用指针打印字符串  printf("%s\n", ptr);  // 使用数组名(数组名相当于指向首元素的指针)打印字符串  printf("%s\n", str);  // 遍历字符串  while (*ptr != '\0') { // '\0' 是字符串的结束符  printf("%c", *ptr);  ptr++; // 移动指针到下一个字符  }  printf("\n");  return 0;  
}

前两行代码可进一步简化成:

char *ptr = "Hello, World!";

这种赋值形式看起来就像数组不存在一样,但本质上都是让ptr指向字符串"Hello, World!"首字符’H’的地址,只不过前者使了数组名这个“中介”。

但两种赋值方式却有一个实质性的关键不同,这一点一定要注意。

就是前者定义的字符串可以改变,而后者字符串会被视为字符串常量,不可以改变。

因为两种初始化方法代表着两种不同的字符串存储方式。不同点如下:

(1)可写性

char str[] = "Hello, World!":这是用数组的方式初始化,字符串存储在程序的可写数据段(如堆或栈)中,因此你可以修改str数组中的字符。

char* ptr = "Hello, World!":因为是直接将字符串的首地址赋给指针,这里的字符串会被当作字符串常量,存储在程序的只读数据段(也称为文本段或代码段)中,因此用这种方式初化始后字符串是不能改变的。如果强行修改会导致未定义行为。如下面的代码:

#include <stdio.h>
int main() {//字符串会被视为常量字符串char *ptr="Hello, Dad!";//试图改变字符串常量,运行会出错*(ptr+8) = 'o'; //指针偏移式赋值ptr[9] = 'g'; //数组式赋值printf("通过指针访问字符串: %s\n", ptr);return 0;
}

如果想用指针修改字符串,数组这个中介就派上用场了。改后代码如下:

#include <stdio.h>
int main() {//数组做中介char str[] = "Hello, Dad!";char *ptr=str;//改变字符时不会出错*(ptr+8) = 'o'; //指针偏移式赋值ptr[9] = 'g'; //数组式赋值printf("通过指针访问字符串: %s\n", ptr);return 0;
}

(2)生命周期

数组如在函数内部声明,它的生命周期就是该函数的执行期间。如在全局或静态上下文中声明,生命周期就是整个程序的执行期间;常量字符串的生命周期是整个程序的执行期间。

四、二维字符数组与字符指针数组

二维字符数组指元素为字符的二维数组。二维数字相当于由行列组成的矩阵,所以二维字符数组实质上是字符的二维矩阵。

二维字符数组常用于存储多个字符串,我们可以用最原始的二维数组初始化方式对二维字符数组赋值。

#include <stdio.h>
int main() {char str[3][10] = {{'h', 'u', 'm', 'a', 'n', '\0'},{'d', 'o', 'g', '\0'},{'c', 'a', 't', '\0'},};printf("%s %s %s\n", str[0], str[1], str[2]);return 0;
}

这种赋值方式实在是太麻烦了,因而简化成这样:

#include <stdio.h>
int main() {char str[3][10] = {"human", "dog", "cat"};printf("%s %s %s\n", str[0], str[1], str[2]);return 0;
}

和字符串赋值一样,也可以通过将字符串的首地址赋给指针来进行赋值。不同的是,现在有多个字符串,因而相应地就要有多个指针,分别指向每个字符串的首地址。

我们可以像之前一样,通过数组这个中介间接指向每个字符串:

#include <stdio.h>
int main() {char str[3][10] = {"human", "dog", "cat"};char* ptr[3];ptr[0]=str[0];ptr[1]=str[1];ptr[2]=str[2];printf("%s %s %s\n", ptr[0], ptr[1], ptr[2]);return 0;
}

上面的ptr数组被叫做:字符指针数组(Character Pointer Array),它是一个一维数组,其元素是指向字符的指针。

也可以抛弃数组,直接指向字符串:

#include <stdio.h>
int main() {char* ptr[3];ptr[0]="human";ptr[1]="dog";ptr[2]="cat";printf("%s %s %s\n", ptr[0], ptr[1], ptr[2]);return 0;
}

不过这种方式还是显得有些麻烦,不如将指针数组的定义与赋值集成为一条语句,于是又简化成这样:

#include <stdio.h>
int main() {char* ptr[3]={"human", "dog", "cat"};printf("%s %s %s\n", ptr[0], ptr[1], ptr[2]);return 0;
}

看起来上面的数组貌似存储的是3个字符串,但你要明白它们存储的其实只是3个指针。

再强调一遍,这个数组只是个一维数组,它是指针数组,即数组的元素都是指针。

同样地,有无数组做中介会影响字符串的可写性。如果通过数组中介赋值,可以改变字符串:

#include <stdio.h>
int main() {char str[3][10] = {"human", "dog", "cat"};char* ptr[3];ptr[0]=str[0];ptr[1]=str[1];ptr[2]=str[2];//用数组做中介,可以改变字符串ptr[1][1] = 'a';ptr[1][2] = 'd';ptr[2][0] = 'm';ptr[2][2] = 'm';printf("%s %s %s\n", ptr[0], ptr[1], ptr[2]);return 0;
}

如果直接将指针指向字符串,没通过数组赋值,这些字符串同样会被视为字符串常量,不能改变。

#include <stdio.h>
int main() {char* ptr[3] = {"human", "dog", "cat"};//指针直接指向字符串时,不可以改变字符串ptr[1][1] = 'a';ptr[1][2] = 'd';ptr[2][0] = 'm';ptr[2][2] = 'm';printf("%s %s %s\n", ptr[0], ptr[1], ptr[2]);return 0;
}

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

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

相关文章

Python基础:【练手小实验系列】个人财务管理系统

文章目录 题目功能解析参考代码题目 设计并实现一个简易的个人财务管理系统,功能如下: 1.收入记录:允许用户输入收入的金额和来源,记录当前总收入; 2.支出记录:允许用户输入支出的金额和用途,记录当前总支出; 3.财务汇总:显示目前的总收入、总支出和净余额; 4.条件查…

【Rust】——项目实例:命令行实例(二)

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

SpringSecurity集成JWT

使用 Spring Security 集成 JWT&#xff08;JSON Web Token&#xff09;身份验证是一种常见的方式来实现基于令牌的身份验证。在 Spring Boot 应用程序中使用 Spring Security 和 JWT&#xff0c;可以创建一个安全、可扩展的身份验证系统。下面是一个示例&#xff0c;展示如何在…

温湿度LCD显示并上传服务器

项目需求 通过温湿度传感器将值传到LCD1602&#xff0c;并实时通过蓝牙透传到手机。 硬件介绍 温湿度传感器 DHT11温湿度传感器 DHT11_温湿度传感器数据格式-CSDN博客 LCD1602LCD1602-CSDN博客 HC-01 继电器模块 硬件接线 LCD1602 D0~D7 --> A0~A7VDD, A --> 5v…

STM32H750外设ADC之双重 ADC 模式

目录 概述 1 双重 ADC 模式介绍 1.1 双重 ADC模式 1.2 双重 ADC 模式的类型 2 双重 ADC 模式寄存器的配置 3 模式功能实现 3.1 注入同步模式 3.2 支持独立注入的常规同步模式 3.2.1 中断的方式 3.2.2 DMA 读取常规数据 3.3 支持独立注入的交替模式 3.3.1 中断触发…

色彩的魔力:渐变色在设计中的无限可能性

夕阳&#xff0c;天空&#xff0c;湖面&#xff0c;夕阳...随着距离和光影的变化&#xff0c;颜色的渐变色&#xff0c;近大远小、近实远虚的透视&#xff0c;为大自然营造了浪漫的氛围。延伸到UI/UX设计领域&#xff0c;这种现实、惊艳、独特的渐变色也深受众多设计师的喜爱。…

setmapAVL树红黑树

目录 关联式容器树形结构的关联式容器setset的模板参数列表set的构造函数set的迭代器set的容量操作set其他操作 multisetmap键值对map的模板参数列表map的迭代器map中元素的修改map的容量与元素访问 multimap底层结构avl树avl树概念AVL树结点的定义AVL树的插入AVL树的旋转AVL树…

python-flask结合bootstrap实现网页小工具实例-半小时速通版

参考&#xff1a; Python之flask结合Bootstrap框架快速搭建Web应用_支持bootstrap的python软件-CSDN博客 https://blog.csdn.net/lovedingd/article/details/106696832 Bootstrap 警告框 | 菜鸟教程 https://www.runoob.com/bootstrap/bootstrap-alert-plugin.html flask框架…

数据结构——7.3 树形查找

7.3 树形查找 概念 二叉排序树&#xff08;BST&#xff09; 二叉排序树&#xff08;Binary Sort Tree&#xff0c;BST&#xff09;&#xff0c;又称为二叉查找&#xff08;搜索&#xff09;树&#xff08;Binary Search Tree&#xff09;&#xff0c;是一种特殊的二叉树&am…

FreeLearning C/C++ 译文集翻译完成

C 高级编程C 高级编程秘籍Qt Creator 应用开发C 游戏编程入门指南C 编程入门指南Boost.Asio C 网络编程Boost C 应用开发秘籍第二版C 数据结构与算法设计原理C Qt5 GUI 编程C 高性能编程C 反应式编程C 系统编程秘籍C 研讨会C 现代嵌入式编程秘籍C 专家编程&#xff1a;成为熟练…

Android,判断是否快速点击

问题背景 在Android控件中,如果快速点击容易造成一些不同的bug,尤其是那种在click事件中方有耗时操作的代码,容易引起anr,并且有些性能低的机器,在用户点击多次控件的时候很容易出现问题,在车机中也会导致回弹的一系列问题(这里面包括get到的信号导致回弹),针对于这种…

力扣---从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]示…

Rust常用特型之Borrow和BorrowMut特型

在Rust标准库中&#xff0c;存在很多常用的工具类特型&#xff0c;它们能帮助我们写出更具有Rust风格的代码。 std::borrow::Borrow和AsRef有点相似&#xff0c;如果一个类型实现了Borrow<T>&#xff0c;那么你可以从它的borrow函数里高效的借出一个&T。但是Borrow施…

synchronized和ReentrantLock傻傻分不清楚

synchronized和ReentrantLock都是用于线程间同步的机制&#xff0c;都是可重入锁&#xff08;同一个线程可以多次获取同一个锁&#xff09;&#xff0c;它们的异同点如下&#xff1a; 一、应用场景 1.synchronized可应用于实例方法、静态方法和代码块。 2.ReentrantLock 是 jav…

使用Docker搭建Redis主从集群

文章目录 ☃️前言☃️搭建❄️❄️架构❄️❄️实例说明❄️❄️搭建第一个服务器上的两个实例❄️❄️搭建第二个服务器上的一个实例 ☃️开启主从❄️❄️改配置❄️❄️重启从节点 ☃️验证 ☃️前言 单节点 Redis 的并发能力是有上限的&#xff0c;要进一步提高Redis的并…

了解监控易(33):工单管理

在复杂的IT运维环境中&#xff0c;高效、规范地处理各种事务请求至关重要。监控易的工单管理功能&#xff0c;作为一款轻量化的运维协同工具&#xff0c;为团队提供了一个集中化、标准化的平台&#xff0c;以创建、接单、转交、挂起和重启工单&#xff0c;从而确保客户设备故障…

搜维尔科技:SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中

SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中 搜维尔科技&#xff1a;SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中 得益于 SenseGlove 的力反馈专利&#xff0c;学员可以感受到他们正在组装的零件的形状、尺寸和密度。学员可以通过运动跟踪…

[大模型]TransNormerLLM-7B FastApi 部署调用

TransNormerLLM-7B FastApi 部署调用 1. TransNormer 介绍 TransNormerLLM 是一个基于线性注意力的 LLM&#xff0c;在准确性和效率方面均优于传统的基于 softmax 注意力的模型。它是在包含多达1.4 万亿个令牌的高质量语料库上进行训练的&#xff0c;TransNormerLLM 从之前的…

K-means和DBSCAN

目录 一、K-means和DBSCAN之间的主要区别 二、DBSCAN聚类算法 2.1DBSCAN聚类算法实现点集数据的聚类 2.2DBSCAN聚类算法实现鸢尾花数据集的聚类 三、K-means聚类算法 3.1K-means聚类算法实现随机数据的聚类 3.2K-means聚类算法实现鸢尾花数据集的聚类 一、K-means和DBSC…

014Node.js时间格式包silly-datetime安装与使用

下载&#xff1a; https://www.npmjs.com/网站上下载silly-datetime 安装 npm i silly-datetime --save var sd require(silly-datetime);console.log(new Date()); //2024-04-18T04:40:38.505Zvar dsd.format(new Date(), YYYY-MM-DD HH:mm);console.log(d); //2024…