指针与字符串
一、数组存放字符
C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中,(例1_1)字符数组归根结底还是一个数组,上节讲到的关于指针和数组的规则同样也适用于字符数组(例1_2)。
二、字符指针
除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串,
例如:char *str = "hello"; 或者: char *str;str = "hello";
字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0 个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。(例2)
三、两类存放字符的区别
它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。另外,字符数组的sieof(str)为数组中元素个数(包括‘\0’),字符指针的sizeof(str)为指针大小。(例3)
四、何种情况使用字符数组还是字符串常量
在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,只能使用字符数组,不能使用字符串常量。(例4)
指针与函数
为什么用指针或数组名作为函数的参数
1、使用由于函数最多有一个返回值,为了得到更多的函数返回值,用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。因此可以得到多个函数的返回值。(例1_1、例1_2、例1_3)
(任务:编写mystrlen,mystrcmp,mystrcat,mystrcpy)
2、像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。
注意:1)将数组作为函数参数时会被弱化成指针,因此传递到函数内部的都是数组指针(地址),所以在函数内部无法通过这个指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。(例2 用数组作函数参数)
2)用数组做函数参数时,它的参数还可以写成下面的形式:(也都是将数组地址传递过来而已)
int max(int Arr[6], int len); 或者 int max(int Arr[], int len);
C语言为什么不允许直接传递数组的所有元素,而必须传递数组指针呢?
参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。这样会严重拖慢程序的效率,因此C语言没有从语法上支持数据集合的直接赋值。
C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。
(例1 返回两个字符串中较长的一个)
注意:1、用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。(例2_1)
2、这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。(例2_2)
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
函数指针的定义形式为:
returnType (*pointerName)(param list);
returnType 为函数返回值类型,pointerNmae 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。注意,( )的优先级高于*,第一个括号不能省略。
(例1)