c语言:数组、字符串与指针
相关内容很多,不断整理。
通过这一部分加深对内存的认知。
指针
常用的就是一维和二维指针,指针有两个属性:存放的值(别人的地址)和自身地址
一维指针指向常量的地址,二维地址指向一维指针的地址。
当指针和数组组合起来的时候,要知道
1、指针存放的是谁的地址;
2、数组存放的数据类型是什么;
指针变量+1,就是指针指向从这个元素的内存到下一个内存,走过的距离看指向元素的大小:比如sizeof(int);
数组
基本概念:数组是多个相同类型数据的集合,并且在内存中分布在连续的地址单元中
数组名字就是数组的首地址,然后利用“内存连续”“数据类型相同”,通过指针++的方式遍历数组的每个数据:
一维数组
int arr1[5] = { 1,2,3,4,5 };
for (int i = 0; i < 5; i++) {
printf("%d:%p\t", i, arr+i);
//地址不断往前+4byte(sizeof(int)=4byte)
}
char ch1[5] = { "abcde" };
for (int i = 0; i < 5; i++) {
printf("%d:%p\t", i, ch1+i);
//地址不断往前+1byte(sizeof(char)=1byte)
}
二维数组
二维数组实际在内存中也是连续的
n*m
个数据,只是用了一个二维指针,
请看方式二:想象成一个二维表格,二维指针指行,一维指针指列,也就是*(a+i)
确定为1+i
行(起始为0),*(*(a+i)+j)
在这一行上找1+j列。
方式三:告诉我们二维数组实际在内存中也是连续的n*m
个数据,通过*p=a
,找到了数组a的首地址,然后通过++
方式遍历了n*m
个数据,因为是int
型,即每个数组元素占据4byte
大小的内存,p
每次+1
,就是地址+4byte
,由于内存连续,所以:p=a[0][0]
,p+1=a[0][1]....p+8=a[3][3]
//两种定义方式
int a[3][3] = { { 1,2,3 },{4,5,6},{7,8,9} };
int b[][3] = { {1,2,3} ,{4,5,6} };
int* p = a;//这里的a先相当于 &a[0][0] &a[0] 而不是&a,&a是整个数组的地址,int*pp=&a
printf("sizeof(**a)=%d\n", sizeof(**a));//4 ==>sizeof(a[0][0]) (*a+1)==>+4
printf("sizeof(*a)=%d\n", sizeof(*a));//4*3=12 ==>sizeof(int* a[3]) a+1==>+12
printf("sizeof(a)=%d\n", sizeof(a));//4*3*3=36 这里的a就是整个数组 &a+1==>+36
printf("sizeof(b)=%d\n", sizeof(b));//4*3*2=24
#if 1 //三种指针遍历方式
//方式一:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d\t", a[i][j]);
}
printf("\n");
}
//方式二:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d\t", *(*(a+i)+j));
}
printf("\n");
}
//方式三:
for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
printf("%d\t", *p + i);
}
printf("\n");
数组+指针:int (*a)[5],int *a[5]
先给结论:
int (*a)[5]
//定义一个int型指针a,a指向一个大小为 5sizeof(int) 的内存;
int *a[5]
定义一个数组,类型为int ,数组名为a ,a也是首地址,大小为5sizeof(int)
这里容易被迷惑,搞不清楚情况,这里到底是个啥;首先要知道符号优先级:
[ ] > *
所以int *a[5] ==> (int*) (a[5])
也就是开辟一块内存里面放一个大小为5的数组,这个数组存放数据类型为int*
,这个数组大小也就是sizeof(int*)*5=20byte
;(指针大小都是4byte
)
int (*a)[10]
则是定义一个int型指针 a
,指向一个大小为10的数组,数组数据为int
型。
要注意 前面int *a[10]
是数组,后者不是,只是一个指针
int* b[] = {1,2,3,4,5};
printf("sizeof(b)=%d\n", sizeof(b)); //20
printf("b=%p\n",b); //008FF88C 指向 1 的指针的地址
//相当于 int* p=&(1); c2[0]=p; 这里&(1)是指1的地址
//当然 这里的1 2 等看作常量,存放存于rodata段,没有对应的变量。
printf("b+1=%p\n", b+1); //008FF890 =008FF88C+4
printf("*b=%p\n", *b); //1
printf("*(b+1)=%p\n", *(b + 1));//2
char* c2[] = {'a','b','c','d' };
printf("sizeof(c2)=%d\n", sizeof(c2)); //16
printf("c2=%p\n", c2); //008FF8E8 指向'a'的指针的地址
//相当于 char* p=&('a'); c2[0]=p;
printf("c2+1=%p\n", c2 + 1); //008FF8EC =008FF8E8+4
printf("*c2=%c\n", *c2); //a
printf("*(c2+1)=%c\n", *(c2 + 1)); //b
int arr2[5] = { 1,2,3,4,5 };
int arr3[7] = { 1,2,3,4,5,6,7 };
int(*c)[5] = arr2;//12345
printf("%p %p %p \n", &arr2[0], &arr2[1], &arr2[2]);
printf("%p %p %p \n", c, c + 1, c + 2);
//007AF614 007AF628 007AF63C 说明:c指向的是一个大小为5的int数组,所以每次+1 移动5*4==20byte
printf("%p %p %p \n", *c, *c+1,*c+2);
//*c==&arr2[0] *c+1==&arr2[1] 即*c指向的是int*
printf("%d %d %d \n", **c, *(*c + 1), *(*c + 2));//1 2 3
c = arr3;//123
printf("%p %p %p \n",&arr3[0], &arr3[1], &arr3[2]); //00A0FD24 00A0FD28 00A0FD2C
printf("%p %p %p \n", c,c+2); //00A0FD24 00A0FD4C 00A0FE64
printf("%p %p %p \n", *c, *c + 1, *c + 6); //00A0FD24 00A0FD28 00A0FD3C
printf("%d %d %d \n", **c, *(*c + 1), *(*c + 6)); //1 2 7
//虽然在监视里面显示c为int[5];但是实际上可以通过*(*c + i)访问后面的内存,这里c=arr3,可以通过*(*c + 6)访问到arr3[6](因为数组的内存连续!)
for (int i = 0; i < 20; i++) {
printf("%d:%p\t", i, *c + i);
//地址不断往前+4byte(int) *c->int
}
char c1[] = { "abcdefg" };
char(*c3)[5]=c1;
for (int i = 0; i < 20; i++) {
printf("%d:%p\t", i, *c3 + i);
//地址不断往前+1byte (char) *c->char
}
测试题:int a[5]={1,2,3,4,5}; int p=(int)(&a+1); printf(“%d”,*(p-1)); 答案为什么是5?
a是一个数组名,也就是数组的首地址。对a进行取地址运算符,得到的是一个指向数组的指针!
也就相当于:int (p) [5] = &a;
p是一个指针,它指向的是一个包含5个int元素的数组!!
那么执行p+1后,p的偏移量相当于 p + sizeof(int) * 5 !!
而程序中强制将指针p转换成一个int 那么 p -1 其实就是 p - sizeof(int)
所以,p -1 指向了数组中得最后一个元素,也就是 5
以上是别人的
&a+1 ==> sizeof(a)=20byte 才是 +1 的长度,a[0]视为起始位置00,即现在p指向的位置是 00+20。之后被强转(int*)也就是此时 sizeof(int)=4byte ,*(p-1) 指向位置是 00+20-04=16 ;{1,2,3,4,5}位置为 0,4,8,12,16 ;因此是5
字符数组与字符串
两者区别在于 有没有’\0’;有’\0’的字符数组就是字符串
char a[6]="hello"这就一个数组。如果是在局部函数中是放在栈中的,不能返回c在主函数中打印的,因为局部函数结束,c[5]的生命周期也就结束了。
char *p=“hello”,是 字符串常量,存于rodata段,所以如果在局部函数中返回p也是可以的,在局部函数结束的时刻它把hello的rodata段中的地址返回,主函数中可以用的。
#include <stdio.h>
#include<string.h>
/*
个人理解:
char b1[2][4]: 开辟一块2*4*sizeof(char)大小内存,存放char数据;
char* say[3] = { "hi","hello","hey" };: 开辟一块3*sizeof(char*)大小内存,存放char*数据;"hi"等是字符串常量,存于rodata段。
若另有char* say2[] = { "hi","hello" }; 则里面的字符串地址相同,即共享相同字符串,节省空间。
*/
char b1[2][4] = {"abc","bcd"}; //二维字符数组
char b2[][4] = { "abc","bcd","cde" };
printf("sizeof(b1)=%p\n", sizeof(b1)); //sizeof(b1)=8
printf("sizeof(b2)=%p\n", sizeof(b2)); //sizeof(b2)=12
char* say[] = { "hi","hello","hey" };
char* say2[] = { "hi","hello" }; // &say[0]==&say2[0] &say[1]==&say2[1]
char* say3[] = { "hey","hello" };//&say3[0]==&say[2] &say[1]==&say3[1]
printf("sizeof(say)=%p\n", sizeof(say)); //sizeof(say)=0000000C ? 3*4
printf("sizeof(say2)=%p\n", sizeof(say2)); //sizeof(say)=00000008 ?2*4
#if 1
for (int i = 0; i < strlen(say); i++) {
printf("say+%d=%p\t", i, say + i);
//say+0=0024F5DC say+1=0024F5E0 say+2=0024F5E4 +4byte是指针大小,say存放的是指针
printf("*(say+%d)=%s\n", i, *(say + i)); //*(say+0)=hi *(say+1)=hello *(say+2)=hey
}
#endif // 1
#if 1
for (int i = 0; i < strlen(say);i++) {
int j = 0;
while (say[i][j]!='\0') {
printf("say[%d][%d]=%c\t ",i,j,say[i][j]);
printf("&say[%d][%d]=%p\n ", i, j, &say[i][j]);
++j;
/*
say[0][0]=h &say[0][0]=003F7B8C
say[0][1]=i &say[0][1]=003F7B8D
say[1][0]=h &say[1][0]=003F7B90
say[1][1]=e &say[1][1]=003F7B91
say[1][2]=l &say[1][2]=003F7B92
say[1][3]=l &say[1][3]=003F7B93
say[1][4]=o &say[1][4]=003F7B94
say[2][0]=h &say[2][0]=003F7B98
say[2][1]=e &say[2][1]=003F7B99
say[2][2]=y &say[2][2]=003F7B9A
*/
}
}
#endif // 1
#if 1// 测试 say[][i]
for (int i = 0; i < 100;++i) {
//内存里面存放是连续的。
//系统怎样分配内存不清楚,i可以加下去,加下去可以出现 say[0][%d]=%c等字符串,即系统把所有字符串都放在一块内存里
printf("say[0][%d]=%c \t ",i, say[0][i]); //0
printf("&say[0][%d]=%p \n ", i, &say[0][i]); //0
}
#endif
#if
int a[] = { 1,2,3,4,5 }; int* p[] = { a,a + 1,a + 2,a + 3 };
int** q = p;//int** q = &a[0] p+2->&p[2] *(p+2)->p[2] **(p+2)-> a+2
int ans = *(p[0] + 1) + **(q + 2);
//q->p首地址 *q->p[0] **q->a[0]
// *(q+1)==>p[0]->p[1] *(*q+1) ==>a[0]->a[1]
// p->p[0] *p->a[]
// *(p+1)==>p[0]->p[1] *p+1 ==>a[0]->a[1]
// *(p[0]+1)==>a[0]->a[1]
#endif