指针与字符串


导读: 字符串可以分配在内存的不同区域,通常使用指针来支持字符串操作。指针支持动态分配字符串和将字符串作为参数传递给函数。

了解声明和初始化字符串的不同方法,研究C程序中字面量池的使用及其影响。此外,我们还会了解比较、复制和拼接字符串等常见字符串操作。

字符串通常以字符指针的形式传递给函数和从函数返回。我们可以用字符指针传递字符串,也可以用字符常量的指针传递字符串,后者可以避免字符串被函数修改。

我们也可以从函数返回字符串,从而满足某个请求。可以将这个字符串从外面传给函数并由函数修改,也可以在函数内部分配,还可以返回静态分配的字符串。

指针是保存字符串地址的。我们可以使用地址索引和接引数据的。

一、字符串基础

字符串是以ASCII字符NUL结尾的字符序列。ASCII字符NUL表示为 \0。字符串通常存储在数组或者从堆上分配的内存中。不过,并非所有的字符数组都是字符串,字符数组可能没有NUL字符。字符数组也用来表示布尔值等小的整数单元,以节省内存空间。C语言中有两种字符串

  • 单字节字符串
    由char数据类型组成的序列。
  • 宽字符串
    由wchar_t数据类型组成的序列。

1、字符串声明

声明字符串的方式有三种:字面量字符数组字符指针。字符串字面量是用双引号引起来的字符序列,常用来进行初始化,它们位于字符串字面量池中。

声明例子如下:

//栈区字符串
char header[32];

//保存字符串的指针
char *header;

2、字符串字面量池

字符串字面量一般分配在只读内存中 (但最好显示声明字符串不可修改),所以是不可变的。字符串字面量在哪里使用,或者它是全局、静态或局部的都无关紧要,从这个角度讲,字符串字面量不存在作用域的概念。

const char*tabHeader="Media Player";
*tabHeader='L'; //会报错,不能休该

内存示意图:
在这里插入图片描述

3、字符串初始化

1、初始化字符数组

//第一种:直接字符字面量赋值
char header[]="Media Player";

//第二种:使用字符操作函数
char header[13];
strcpy(header,"Media Player");

//第三种:逐个赋值
header[0]='M';
header[1]='e';
header[12]='\0';

2、初始化字符指针

在使用malloc函数分配空间后,这个内存空间不会主动设置字符串结尾的标志 都是要手动设置的。

  • 一定要存在正确的字符串分配地址【不可未分配地址就赋值】
  • 一定要记得算上终结符NUL。
  • 不要用sizeof操作符,而是用strlen函数来确定已有字符串的长度。
    sizeof操作符会返回数组和指针的长度,而不是字符串的长度。
//使用malloc函数分配空间
char *header=(char*)malloc(strlen("Media Player")+1);

//第一种:直接字符字面量赋值
char*header="Media Player";

//第二种:使用字符拷贝函数和字面量赋值
strcpy(header,"Media Player");

//第三种:逐个赋值
*(header+0)='M';
*(header+1)='e';
.....
*(header+12)='\0';

3、从其他输入初始化字符串

其实就是从文件、用户输入等方面获取的字符串。初始化和前面两种差不多,只是来源不一样,上面是程序写死的,这个是开放的。

//从控制台输入 命令
#define StringLength 100

char*command = (char*)malloc(sizeof(char) * StringLength +1);;

printf("Enter a Command:");
scanf("%s",command);

4、字符串位置小结

下面是一个字符字面量、数组和指针的调用例子。

char*globalHeader="Chapter";
char globalArrayHeader[]="Chapter";
void displayHeader()
{
	static char* staticHeader="Chapter";
	char* localHeader="Chapter";
	static char staticArrayHeader[]="Chapter";
	char localArrayHeader [ ] = "Chapter" ;
	char*heapHeader=(char*)malloc(strlen("Chapter")+1);
	strcpy(heapHeader,"Chapter");
}

内存示意图:
在这里插入图片描述

二、标准字符串操作

下面几个函数都定义于头文件 <string.h>

下面情况要注意:

  • 不能使用char类型变量的地址 : currentPath=strcat(path, ‘\’);

1、比较字符串

//定义
int strcmp( const char *s1, const char *s2);

原理:
相互比较的方法是被比较的字符串中首对不同字符(都转译成 unsigned char )的值间的差的符号。

返回值:

  • 负数
    如果按字典序(字母序)s1比s2小就返回负数。
  • 0
    如果两个字符串相等就返回0。
  • 正数
    如果按字典序s1比s2大就返回正数。

实例代码:

#include <string.h>
#include <stdio.h>
 
void demo(const char* lhs, const char* rhs)
{
    int rc = strcmp(lhs, rhs);
    const char *rel = rc < 0 ? "precedes" : rc > 0 ? "follows" : "equals";
    printf("[%s] %s [%s]\n", lhs, rel, rhs);
}
 
int main(void)
{
    const char* string = "Hello World!";
    demo(string, "Hello!");
    demo(string, "Hello");
    demo(string, "Hello there");
    demo("Hello, everybody!" + 12, "Hello, somebody!" + 11);
}

输出:

[Hello World!] precedes [Hello!]
[Hello World!] follows [Hello]
[Hello World!] precedes [Hello there]
[body!] equals [body!]

2、复制字符串

char* strcpy(char*s1,const char*s2);

复制 s2 所指向的空终止字节字符串,包含空终止符,到首元素为 s1 所指的字符数组。

注意要点:

  • s1 的大小要大于 s2 的大小,以防止截断;
  • 可以使用安全的 strcpy_s()函数;

3、拼接字符串

char *strcat(char*s1,const char*s2);

此函数把第二个字符串拼接到第一个的结尾,第二个字符串是以常量char指针的形式传递的。
注意要点:

  • 函数不会分配内存,这意味着第一个字符串必须足够长,能容纳拼接指针和字符串后的结果,否则函数可能会越界写入,导致不可预期的行为。
  • 函数的返回值的地址跟第一个参数的地址一样。这在某些情况下比较方便,比如这个函数作为printf函数的参数时。

三、传递字符串

1、传递简单字符串

size_t stringLength(char*string)
{
	size_t length=0;
	while(*(string++))
	{
		length++;
		return length;
	}
}

char simpleArray[]="simple string";
char*simplePtr=(char*)malloc(strlen("simple string")+1);
strcpy(simplePtr,"simple string");

//调用的方式
printf("%d\n",stringLength(simplePtr));
printf("%d\n",stringLength(simpleArray));
printf("%d\n",stringLength(&simpleArray));
printf("%d\n",stringLength(&simpleArray[0]));

程序内存示意图:
在这里插入图片描述

2、传递字符常量的指针

传递字符常量就是为了安全;防止程序非法修改字符串

就像字符串函数 strcat;

char *strcat(char*s1,const char*s2);
char* strcpy(char*s1,const char*s2);

这个s2就是源字符串;s1就是目的字符串
标准库里面为了安全就是 使用 const 来防止源字符串被修改

3、传递需要初始化的字符串

有些情况下我们想让函数返回一个由该函数初始化的字符串。假设我们想传递一个
部件的信息,比如名字和数量,然后让函数返回表示这个信息的格式化字符串。通
过把格式化处理放在函数内部,我们可以在程序的不同部分重用这个函数。
不过,我们得决定是给函数传递一个空缓冲区让它填充并返回,还是让函数动态分
配缓冲区并返回。
要传递缓冲区,需要一些条件:

  • 必须传递缓冲区的地址和长度【定位字符串地址,防止访问越界】
  • 调用者负责释放缓冲区【防止内存泄漏】
  • 函数通常返回缓冲区的指针

4、给应用程序传递参数

main函数通常是应用程序第一个执行的函数。对基于命令行的程序来说,通过为其
传递信息来打开某种行为的开关或控制某种行为很常见。可以用这些参数来指定要
处理的文件或是配置应用程序的输出。比如说,Linux的 ls 命令会基于接收到的参
数列出当前目录下的文件。
C用传统的argc和argv参数支持命令行参数。第一个参数argc,是一个指定传递
的参数数量的整数。系统至少会传递一个参数,这个参数是可执行文件的名字。第二
个参数argv,通常被看做字符串指针的一维数组,每个指针引用一个命令行参数。
下面的main函数只是简单地列出了它的参数,每行一个。在这个版本中,argv被
声明为一个char指针的指针。

int main(int argc, char** argv)
{
	for(int i=0;i<argc;i++)
	{
		printf("argv[%d]%s\n",i,argv[i]);
	}
}

程序可以用下面的命令执行:

process.exe-f names.txt limit=12 -verbose

输出如下:

argv[0] c:/process.exe
argv[1] -f
argv[2] names.txt
argv[3] limit=12
argv[4] -verbose

四、返回字符串

函数返回字符串时,它返回的实际是字符串的地址。这里应该关注的主要问题是如
何返回合法的地址,要做到这一点,可以返回以下三种对象之一的引用:

  • 字面量
  • 动态分配的内存
  • 本地字符串变量

1、返回字面量的地址

这个很好理解,举例下面的写法,就是返回的字面量

char* returnALiteral(int code)
{
	switch(code)
	{
		case 100:
			return"Boston Processing Center";
		case 200:
			return"Denver Processing Center";
		case 300:
			return "Atlanta Processing Center";
		case 400:
			return"San Jose Processing Center";
	}
}

2、返回动态分配内存的地址

如果需要从函数返回字符串,我们可以在堆上分配字符串的内存然后返回其地址。
如下代码

char*blanks(int number)
{
	char*spaces=(char*)malloc(number+1);
	int i;
	for(i=0;i<number;i++)
	{
		spaces[i] =' ';
	}
	spaces[number]='\0';
	return spaces;
}
//调用
char*tmp=blanks(5);

程序内存示意图:
在这里插入图片描述
释放返回的内存是函数调用者的责任,如果不再需要内存但没有将其释放会造成内存泄漏。下面是一个内存泄漏的例子,printf函数中使用了字符串,但是接着它的地址就丢失了,因为我们没有保存:

printf("[%s]\n",blanks(5));

一个更安全的方法如下所示:

//这里的tmp就保存这个地址,可能后面还需要操作
char*tmp=blanks(5);
printf("[%s]\n",tmp);

//其他操作.........

free(tmp);

注意要点:

  • 返回局部字符串的地址可能会有问题,如果内存被别的栈帧覆写就会损坏。会读取错误
  • 返回静态成员没问题,但是没有堆对象灵活。
  • 10
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳风暴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值