1. 字符串 字符串数组

1. 字符串

#include <stdio.h>

#define MSG "I am a symbolic string constant."
#define MAXLEN 81


int main(int argc, char const *argv[])
{
	char words[MAXLEN] = "I am a string in an array.";
	char *ptr1 = "Something is pointing at me.";
	puts("Here are some strings");
	puts(MSG);
	puts(words);
	puts(ptr1);

	printf("\n");
	return 0;
}

输出结果:

Here are some strings
I am a symbolic string constant.
I am a string in an array.
Something is pointing at me.

注意:puts() 函数和 printf() 不同的是,puts() 函数只显示字符串,而且自动在显示的字符串末尾加上换行符

2.定义字符串

a. 定义字符串常量

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

  1. 如果字符串常量之间没有间隔,或者用空白字符分隔,c会将其视为串联起来的字符串常量*
    下面两种声明是等价的

    char greetinf[50] = "hello ""and" " how are y""ou!"
    char greetinf[50] = "hello and how are you!"
    
  2. 如果要在字符串内部使用双引号,则必须要在双引号前面加上一个反斜杠 ( \ )

    char greetinf[50] = "\"hello\" and \"how are you!\""
    

    输出结果:

    "hello" and "how are you!"
    
  3. 字符串属于静态存储类别,这说明 如果函数中使用字符串常量,该字符只会被存储一次,在整个程序的生命周期内存在,即使函数被调用多次。用双引号括起来的内容 被视为 指向该字符串存储位置 的指针,这类似于把数组名作为指向该数组的指针

    printf("%s, %p, %c\n", "We", "are", *"hello word");
    

    输出结果:

    We, 0x55fa55b659c1, h
    

    %s 打印字符串 We ,
    %p 打印字符串 “are” 所在的地址(用双引号括起来的内容 被视为 指向该字符串存储位置 的指针),
    %c 打印 ‘h’ 这个字符,“hello word” 表示该字符串所在的地址,* “hello word” 则表示该字符串所指向地址上存储的值,%c 只读取一个字符 h (字符串的地址 也就是 首字符所在的地址:这类似于把数组名作为指向该数组的指针)

b. 定义字符串数组和初始化

定义字符串数组时,必须要让编译器知道你需要多少空间,有两种方式存储字符串。

  1. 创建足够空间的数组存储字符串,用指定字符串初始化数组buf[ ]

    char buf[40] = "I am a string in an array.";
    

    这样初始化,比标准数组初始化简单,以下是用数组标准初始化

    char buf2[40] = {'I', ' ', 'a', ········, '\0'}
    

    注意:
    用标准数组初始化方法,最后一个字符是 ‘\0’ ,如果没有 ‘\0’ 这就不是一个字符串,而是一个字符数组。所以在指定数组大小时,要确保数组元素个数至少比字符串长度多1(容纳空字符)。所有未使用元素初始化的元素都被自动初始化为0(这里0是char形式的空字符 \0 ,不是数字字符0)

    数组初始化声明中的大小是可以省略的,这样编译器自动计算大小。让编译器确定初始化字符数组的大小是合理的,因为处理字符串的函数通常都不知道数组的大小,这些函数是通过查找字符串末尾的空字符确定字符串在何时结束。

    注意:让编译器确定数组大小的只能用在初始化数组时,如果想先创建再填充数组,就必须要在声明的时候指定数组大小,这里又要注意数组大小必须是 整型常量或者整型常量的组合。
    

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

    char name[10] = "xiaoming";
    

    则有以下关系对应:

    name = &name[0]	*name = name[0] = 'x' *(name + 1) = name[1] = 'i'
    
  2. 使用指针方法创建字符串

    char *ptr1 = "Something is pointing at me.";
    char ptr2[] = "Something is pointing at me.";
    

    以上两种声明几乎相同,ptr1 ptr2都是该字符串的地址,其中带双引号的字符串本身决定了预留给字符串的存储空间

c. 数组和指针

  1. 数组形式 ( ptr1[ ] ) 在计算机内存中分配一个内含29个元素的数组(每个元素对应一个字符,末尾还有一个空白字符 ‘\0’),每个元素被初始化为字符串常量对应的字符。通常,字符串都作为可执行程序的一部分存储在数据段中,当把程序载入内存时,也载入了程序中的字符串,字符串存储在静态存储区,但是程序在开始运行时才会为该数组分配内存,此时才将字符串拷贝到数组
    注意:此时字符串有两个副本:一个是静态内存中的字符串常量,另一个是存储在ptr1数组中的字符串。

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

  2. 指针形式 ( *ptr2 )也使得编译器为字符串在静态存储区预留了29个元素 的空间,程序开始执行后,会为指针变量 ptr2 开辟一块空间,并把字符串的地址赋值给指针变量,该变量最初指向字符串的首字符,它是指针变量,因此可以改变,可以使用自增运算符,即++ptr2 指向第二个字符。

  3. 字符串常量被视为const数据,由于 ptr2 指向这个const数据,所以应该把 ptr2 声明为const类型的指针,这意味着 不能用ptr2改变它指向的数据,但是ptr2本身是可以改变的(也就是 *ptr2 不可重新赋值,但是ptr2本身是可以改变的) ,如果把字符串拷贝给数组,那就可以随便改变数据,除非数组时const。

    总结:初始化数据是把静态存储区的字符串拷贝到数组中,初始化指针只把字符串的地址拷贝给指针
    看下面例子:

    #include <stdio.h>
    
    #define MSG "aaa"
    
    
    int main(int argc, char const *argv[])
    {
    	char ptr1[] = MSG;
    	char *ptr2 = MSG;
    
    	printf("\"aaa\"	%p\n", "aaa");
    	printf("MSG	%p\n", MSG);
    	printf("ptr1	%p\n",	ptr1);
    	printf("ptr2	%p\n", ptr2);
    	printf("\"aaa\"	%p\n", "aaa");
    	return 0;
    }
    

    运行结果

    "aaa"   0x5654ae0ec7f4
    MSG     0x5654ae0ec7f4
    ptr1    0x7ffd56d0a934
    ptr2    0x5654ae0ec7f4
    "aaa"   0x5654ae0ec7f4
    

    从上面结果可以看出:
    第一:ptr2 和 MSG的地址相同,而ptr1的地址不同,这与总结的一致
    第二:字符串常量 “aaa” 在程序中两个 printf() 函数中出现了2次,但是编译器只使用了一个位置来存储,并且与MSG的地址相同。编译器可以把多次使用的相同字符串常量存储在一处或者多处,另一个编译可能在不同位置存储"aaa"
    第三:静态数据使用的内存和ptr1使用的动态内存不同,不仅值不同,不同的编译甚至使用不同的位数表示两种内存。

d.数组和指针的区别

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

  1. 看下面例子

    char heart[] = "i love Tom";
    char *head = "i love Tim";
    

    都可以使用数组表示法:

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

    输出结果:

    i lov
    i lov
    

    都能进行指针加法操作

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

    输出结果:

    i lov
    i lov
    

    只有指针表示法可以递增

    while(*(head))
    	putchar(*(head++));
    putchar('\n');
    

    输出结果:

    i love Tim
    

    数组的元素是变量(除非数组被声明为const),数组名是常量,不可改变,不可做左值使用

    heart[0] = 'I';
    puts(heart);
    

    输出结果:

    I love Tom
    

    指针变量不可使用数组表示法修改字符串内容

    head[0] = 'I';	//不允许,非法操作
    puts(head);
    

    输出结果:

    Segmentation fault (core dumped)
    

    注意:
    1.指针初始化字符串变量时 最好使用const限定符

    	const char * ptr1 = MSG;
    

    2. 如果不修改字符串内容,尽量不要使用指针指向字符串常量。

e. 字符串数组
指针数组,可以通过下标访问多个不同字符串。

#include <stdio.h>

#define LEN 4
#define SIZE 10

int main(int argc, char const *argv[])
{
	const char *buf1[LEN] = {"hello", "world", "char", "array"};

	const char buf2[LEN][SIZE] = {"name", "age", "sex", "number"};

	int i;
	for (i = 0; i < LEN; ++i)
	{
		printf("%s\t", buf1[i]);
	}
	putchar('\n');

	for (int i = 0; i < LEN; ++i)
	{
		printf("%s\t", buf2[i]);
	}
	putchar('\n');
	
	printf("buf1 size : %ld\n", sizeof(buf1));
	printf("buf2 size : %ld\n", sizeof(buf2));
	return 0;
}

输出结果:

hello   world   char    array
name    age     sex     number
buf1 size : 32
buf2 size : 40

对于指针表示法的字符串数组buf1,内含4个指针的数组,共占用32个字节
对于数组表示法的字符串数组buf2,内含4个数组的数组,每个数组占用10个字节,共占用40个字节。
buf1[0] 和 buf2[0] 都表示第一个字符串,但是它们的类型并不相同,buf1中的指针指向初始化时所用的字符串自常量的位置,这些字符串被储存在静态内存中;buf2中的数组则存着字符串常量的副本,所以每个字符串都被存储了两次。此外 为字符串数组分配内存的使用率低,buf2中的每个元素大小相同,且必须是能存储最长字符串的大小。
可以把buf2理解为矩形二维数组,每行长度都是10,空白元素都是用 ‘\0’ 补全,而buf1则为不规则数组,每行的长度不同,

综上:
如果只是显示一系列字符串常量,使用指针数组,比二维字符数组效率更高,但是指针数组指向的字符串常量不可以修改
如果要改变字符串或者为字符串输入预留空间,则使用二维字符数组,不要使用指针数组。

f.指针和字符串

#include <stdio.h>


int main(int argc, char const *argv[])
{
	const char * buf = "it is string.";
	const char * copy;
	copy = buf;

	printf("buf = %s\t &buf = %p\t value = %p\n", buf, &buf, buf);
	printf("copy = %s\t &copy = %p\t value = %p\n", copy, &copy, copy);
	return 0;
}

输出结果:

buf = it is string.      &buf = 0x7ffffde2ab08   value = 0x561ae50237c8
copy = it is string.     &copy = 0x7ffffde2ab10  value = 0x561ae50237c8

第一项打印字符串,都是 it is string.
第二项 是两个指针的地址
第三项 是指针的值,也就是它存储的地址,buf 好 copy都是 0x561ae50237c8 ,说明都指向同一个位置,也说明程序并没有拷贝字符串,而是让copy的指针指向buf所指向的字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值