《深入理解C指针》——指针和字符串

字符串基础

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

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

wchar_t数据类型用来表示宽字符,要么是16位宽,要么是32位宽。这两种字符串都以NUL结尾。可以在string.h中找到单字节字符串函数,而在 wchar.h中找到宽字符串函数。除非特别指明,本章用到的都是单字节字符串。创建宽字符主要用来支持非拉丁字符集,对于支持外语的应用程序很有用。

NULL和NUL不同。NULL用于表示特殊的指针,通常定义为((void*)0),而NUL是一个char,定义为\0,两者不能混用。

字符常量是单引号引起来的字符序列。字符常量通常由一个字符组成,也可以包含多个字符,比如转义字符。在C中,它们的类型是int,如下所示:

printf("%d\n", sizeof(char));	// 1
printf("%d\n", sizeof('a'));	// 4

执行上述代码可以看到char的长度是1,而字符字面量的长度是4。这个看似异常的现象乃语言设计者有意为之。

字符串声明

声明字符串的方式有三种:字面量、字符数组和字符指针。
字符串字面量是用双引号引起来的字符序列,常用来进行初始化。
不要把字符串字面量和单引号引起来的字符搞混——后者是字符字面量。

char header[32];	
char *header;

字符串字面量池

定义字面量时通常会将其分配在字面量池中,这个内存区域保存了组成字符串的字符序列。多次用到同一个字面量时,字面量池中通常只有一份副本。这样会减少应用程序占用的内存。通常认为字面量是不可变的,因此只有一份副本不会有什么问题。不过,认定只有一份副本或者字面量不可变不是一种好做法,大部分编译器有关闭字面量池的选项,一旦关闭,字面量可能生成多个副本,每个副本拥有自己的地址。
在这里插入图片描述
字符串字面量一般分配在只读内存中,所以是不可变的。字符串字面量在哪里使用,或者它是全局、静态或局部的都无关紧要,从这个角度讲,字符串字面量不存在作用域的概念。
字符串字面量不是常量的情况
在大部分编译器中,我们将字符串字面量看做常量,无法修改字符串。不过,在有些编译器中(比如GCC),字符串字面量是可修改的。看下面这个例子:

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n" ,tabHeader); /打印"Lound"

为了避免这种情况,可以定义变量为const:

const char *tabHeader = "Sound";

字符串初始化

初始化字符串采用的方法取决于变量是被声明为字符数组还是字符指针,字符串所用的内存要么是数组要么是指针指向的一块内存。我们可以用字符串字面量或者一系列字符初始化字符串,或者从别的地方(比如说标准输入)得到字符。

初始化char数组
char header[] = "Media Player";

在这里插入图片描述

char header[13];
strcpy(header, "Media Player");
初始化char指针
char *header;
# 
char *header = (char*)malloc(strlen("Media Player")+1);
strcpy(header, "Media Player"); 

在这里插入图片描述

在决定malloc函数要用到字符串长度时,要注意以下事项:

  • 一定要记得算上终止符NUL。
  • 不要用sizeof操作符,而是用strlen函数来确定已有字符串的长度。sizeof操作符会返回数组和指针的长度,而不是字符串的长度。
char *header = "Media Player";

在这里插入图片描述
试图用字符字面量来初始化char指针不会起作用。因为字符字面量是int类型,这其实是尝试把整数赋给字符指针。这样经常会造成应用程序在解引指针时终止:

char* prefix = '+'; //不合法

正确的做法是像下面这样用malloc函数:

prefix = (char* )malloc(2) ;
prefix = '+';
*(prefix+1)= 0;

标准字符串操作

比较字符串

int strcmp(const char *s1, const char *s2);

复制字符串

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

拼接字符串

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

传递字符串

传递简单字符串

size_t stringLength(char* string);
size_t stringLength(char string[]);

传递字符串常量的指针

以字符常量指针的形式传递字符串指针是很常见也很有用的技术,这样可以用指针传递字符串,同时也能防止传递的字符串被修改。

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

传递需要初始化的字符串

  • 给函数传递一个空缓冲区让它填充并返回:
char* format(char *buffer, size_t size,
const char* name, size_t quantity, size_t weight) { snprintf( buffer, size,"Item: %s Quantity: %u weight: %u", name, quantity, weight);
	return buffer;
}

printf("%s\n" , format(buffer,sizeof(buffer), "Axle", 25, 45)) ;
  • 让函数动态分配缓冲区并返回:
char* format(char *buffer, size_t size,
const char* name, size_t quantity, size_t weight) {
	char *formatString = "Item: %s Quantity: %u weight: %u";
	size_t formatStringLength = strlen(formatstring)-6;
	size_t nameLength = strlen(name) ;
	size_t length = formatStringLength + nameLength + 10 + 10 + 1;
	if(buffer == NULL) {
		buffer = (char*)malloc(length);
		size = length;
	}
	snprintf(buffer, size, formatString, name, quantity, weight);
	return buffer;
}

第二种方法的主要缺点在于调用者现在要负责释放分配的内存,调用者需要对函数的使用方法了如指掌,否则可能很容易产生内存泄漏。

给应用程序传递参数

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

返回字符串

返回字面量的地址

针对多个不同目的的返回同一静态字符串的指针可能会有问题。

char* staticFormat(const char* name, size t quantity, size_t weight) {
	static char buffer[64]; //假设缓冲区足够大
	sprintf(buffer, "Item: %s Quantity: u weight: %u", 
name, quantity, weight);
	return buffer;
}

char* part1 = staticFormat("Axle".25,45);
char* part2 = staticFormat("Piston",55,5);
printf("%s\n", part1); //Item: Piston Quantity: 55 weight: 5
printf("%s\n", part2); //Item: Piston Quantity: 55 weight: 5

staticFormat两次调用都使用同一个静态缓冲区,后一次调用会覆写前一次调用的结果。

返回动态分配内存的地址

char* blanks (int numbery {
	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));

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

char *tmp = blanks(5);
printf("[%s]\n",tmp);
free(tmp);
返回局部字符串的地址

返回局部字符串的地址可能会有问题,如果内存被别的栈帧覆写就会损坏,应该避免使用这种方法,这里作解释只是为了说明实际使用这种方法的潜在问题。

#define MAX_TAB_LENGTH 32

char* blanks(int number) i
	char spaces[MAX_TAB_LENGTH];
	for (i = 0; i < number && i < MAX_TAB_LENGTH; i++) {
		spaces[i]=' ';
	}
	spaces[i] = '\0';
	return spaces;
}

执行函数后会返回数组的地址,但是之后下一次函数调用会覆写这块内存区域。解引指针后该内存地址的内容可能已经改变。

在这里插入图片描述

函数指针和字符串

typedef int (fptroperation) (const char*, const char*);
void sort(char *array[], int size, fptrOperation operation){
	int swap = 1;
	while(swap){
		swap = 0;
		for(int i-0; i<size-1; i++) {
			if(operationiarray[i],array[i+11)>0){
				swap =1;
				char *tmp = array[i];
				array[i] = array[i+1];
				array[i+1] = tmp;
			}
		}
	}
}

void displayNames ( char* names[], int size) {
	for(int i=e; i<size; i++) {
		printf(%S", names[i]);
	}
	printf("\n");
}

char* names [] = {"Bob","Ted" ,"carol", "Alice","alice"};
sort(names,5,compare);
displayNames(names ,5);
# Alice Bob Carol Ted alice
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值