C语言数组指针 (指向数组的指针)
数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。
#include <stdio.h>
int main (void)
{
int arr[4] = {1,2,3,4};
printf("arr=%p %p %p \n", arr, &arr[0], &arr[1]);
return 0;
}
//运行结果——明白连续地址的概念了
0018FF38
0018FF38
0018FF3C
Press any key to continue
数组名的本意是表示整个数组,也就是表示多份数据的集合,但在使用过程中经常会转换为指向数组第 0 个元素的指针,所以我们说数组名可以认为是一个指针,但并不就是。也就表示了数组名和数组首地址并不总是等价。
下面的例子演示了如何以指针的方式遍历数组元素:
#include <stdio.h>
int main (void)
{
int arr[] = {1,2,3,4};
int len = sizeof(arr) / sizeof(int);
int *p = arr;
for (int i = 0; i < 4; i++) {
printf("%d ", *(p+i));
}
printf("\n");
return 0;
}
如果一个指针指向了一个数组,我们就称它为数组指针(Array Pointer)。
需要注意的是:
sizeof(arr) / sizeof(int)
不能改成 sizeof(p) / sizeof(int)
数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用sizeof(p) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
也就是说,根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。
总结
引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。
1) 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
2) 使用指针
也就是使用 (p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 (arr+i) 来访问数组元素,它等价于 *(p+i)。
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。
更改上面的代码,借助自增运算符来遍历数组元素:
#include <stdio.h>
int main(){
int arr[] = { 99, 15, 100, 888, 252 };
int i, *p = arr, len = sizeof(arr) / sizeof(int);
for(i=0; i<len; i++){
printf("%d ", *p++ );
}
printf("\n");
return 0;
}
第 8 行代码中,p++ 应该理解为 (p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的。
对指针变量的运算,实际上是对变量中存储的地址的运算。int a = 1;
内存给a分配的地址编号是0018FF44, int *p = &a;
故指针变量p中保存的是0018FF44,这个地址在内存中对应的数据是1(a); 语句p + 1,代表的是 对指针变量中存放的地址 + 4(int 内存4字节);就是现在指针变量中存放的地址是00FF48,至于这个地址对应的数据是多少,就不清了,如果没有定义的话。
指针指向的数据运算符 * 和 自增++ 运算符优先级问题
获取指针指向的数据运算符 * 是低于 自增运算符++的
思考
假设 p 是指向数组 arr 中第 n 个元素的指针,那么 p++、++p、(*p)++ 分别是什么意思呢?
*p++
#include <stdio.h>
int main (void)
{
int arr[] = {3,8,11,33};
int len = sizeof(arr) / sizeof(int);
int *p = &arr[1];
printf("%d\n", *p++);-------------- 3
// 根据运算符优先级 *(p++)
// * 取指针所存储的地址的数据
// ++ 也就是 += 1
// 对指针变量的运算,就是对指针变量存储的地址的运算,
// p++ 也就是 p += 1; 也就是 p=p+1; 也就是指针变量p所存储的地
//址+4 (1个int占4字节);
//数组中变量在内存中排列是连续的,也就是a[0] 占4个字节,,内存中
//下4个字节是a[1],依次排列,
// ++ 后自增运算符 表示先用变量的值,再之后对变量+1
// 所以这里 *(p++) 表达式的值就是 *p的值,也就是arr[1],即3
// 但是指针 p 指向了下一个数,也就是说 现在的p=&a[2]; *p即8
printf("%d\n", *p);------------------- 8
printf("\n");
return 0;
}
*++p
#include <stdio.h>
int main (void)
{
int arr[] = {3,8,11,33};
int len = sizeof(arr) / sizeof(int);
int *p = &arr[1];
printf("%d\n", *++p); ------ 8
printf("%d\n", *p); ------ 8
//优先级 *(++p)
// ++p 取之后的值
// 也就是表达式的值现在为 *(p+1) 也就是 a[2]
// 所以 *p 也是a[2]
printf("\n");
return 0;
}
(*p) ++
#include <stdio.h>
int main (void)
{
int arr[] = {3,8,11,33};
int len = sizeof(arr) / sizeof(int);
int *p = &arr[1];
printf("%d\n", (*p)++); --- 8
printf("%d\n", *p); ---- 9
printf("%d\n", arr[1]); ---- 9
// 优先级 括号优先级最高,所以先处理(*p) 也就是 a[1]的值
// (*p)++ 也就是 a[1]++,表达式先取a[1]的值,然后对a[1]+=1
// 也就是说现在 a[1]由原来的 8 变成了 9;
printf("\n");
return 0;
}
C语言字符串指针(指向字符串的指针)
C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中
char str[] = "hello world";
除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串,例如:
char *str = "hello world";
字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0 个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。
strlen 和 sizeof 的区别
sizeof —— 内存
strlen——字符个数
sizeof() C语言中判断数据类型或者表达式长度符;不是一个函数,字节数的计算在程序编译时进行,而不是在程序执行的过程中才计算出来。
用法
sizeof(类型说明符,数组名或表达式);
或sizeof (变量名);
定义
sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。
深入理解sizeof操作符
strlen (C语言函数)
strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符’\0’为止,然后返回计数器值(长度不包含’\0’)。
strlen只能用char*做参数,且必须是以”\0”结尾的。
char aa[10]=”jun”; cout<
两种表示字符串方式的区别
区别1
#include <stdio.h>
#include <string.h>
int main (void) {
char *str1 = "hello world";
char str2[] = "hello world";
int len = sizeof(str1);
int len1 = strlen(str1);
int len2 = sizeof(str2);
printf("%s \n%d\n%d\n%d\n", str1, len, len1, len2);
return 0;
}
区别2
它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。
#include <stdio.h>
int main(){
char *str = "Hello World!";
str = "I love C!"; //正确
str[3] = 'P'; //错误
return 0;
}
到底使用字符数组还是字符串常量
在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。
最后总结一下,C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。