假冒彩票网站开发,网站建站建设上海黔文信息科技有限公司30,广东网站建设公司报价,关键词排名优化网站建设公司使用指针访问数组
指针类型的加减运算可以使指针内保存的首地址移动。 指针类型加n后。首地址向后移动 n * 步长 字节。 指针类型减n后。首地址向前移动 n * 步长 字节。 步长为指针所指向的类型所占空间大小。 例如#xff1a;
int *p (int *)100;p 1#xff0c;结果为首…使用指针访问数组
指针类型的加减运算可以使指针内保存的首地址移动。 指针类型加n后。首地址向后移动 n * 步长 字节。 指针类型减n后。首地址向前移动 n * 步长 字节。 步长为指针所指向的类型所占空间大小。 例如
int *p (int *)100;p 1结果为首地址向后移动sizeof(int)字节即104。p - 1结果为首地址向前移动sizeof(int)字节即96。
因此指针加减运算对于访问在内存中连续排布的数据对象非常方便。 而数组这种数据对象每个元素在内存中一定是连续排布的。下面我们来探究怎样使用指针访问数组。
使用第一个元素获取数组首地址
既然数组元素在内存中的存储可以保证是连续的那么第一个元素的首地址就是整个数组的首地址。
#include stdio.h
int main()
{int arr[5] { 111, 222, 333, 444, 555 };int* p arr[0];printf(%d\n, *p); // 第1个元素printf(%d\n, *(p 1)); // 第2个元素printf(%d\n, *(p 2)); // 第3个元素printf(%d\n, *(p 3)); // 第4个元素printf(%d\n, *(p 4)); // 第5个元素return 0;
}我们可以使用取地址运算符获取第一个元素的首地址和空间大小即获取一个 int * 类型的指针。 通过取值运算符*可以使用指针中的首地址和空间大小访问或修改目标数据对象。 表达式 p 1 必须先被括号包裹再使用取值运算符*。 这是因为取值运算符*的优先级高于算术运算符。 我们需要先让首地址移动再进行取值操作。 若不使用括号*p会先被取值之后值再被加1。
不使用括号 *p的值为111*p 1的结果为112。 使用括号 (p 1) 使得首地址移动到第二个元素 *(p 1) 得到结果为222。
使用数组名获取数组首地址
#include stdio.h
int main()
{int arr[5] { 111, 222, 333, 444, 555 };int* p arr;printf(sizeof arr %d\n, sizeof(arr));printf(sizeof p %d\n, sizeof(p));printf(sizeof arr 1 %d\n, sizeof(arr 1));return 0;
}使用32位进行编译 sizeof arr 20
sizeof p 4
sizeof arr 1 4arr 的大小为20。 p 的大小为4。 arr 1 的大小为4。 p 是一个指针大小为4是理所当然的。 但是 arr 的大小为20那么arr应该不是一个指针类型才对但是它却又可以成功赋值给 int * 。 而 arr 1 的大小却又为4。
类型为“以T为元素的数组arr”与“指向T的指针p”的关系。
当数组名arr出现在一个表达式当中数组名arr将会被转换为指向数组第一个元素的指针。但是这个规则有两个例外
对数组名arr使用sizeof时。对数组名arr使用时。
也就是说数组名arr的类型其实是 int [5] 因此 sizeof(arr) 的结果才会是20。 数组名arr出现在表达式int* p arr中会被转换为指向数组第一个元素的指针即 int [5] 转为 int * 类型。之后进行赋值运算。 arr 1 也是一个表达式数组名 arr 被转换为 int * 类型进行加法运算后仍然为 int * 类型。
使用指针访问数组等价于下标访问
现在我们学会了访问数组元素的两种办法
数组名[下标]*(数组名 偏移量)
其中偏移量就是指针指向的地址与数组首地址之间相差几个元素。 事实上这两种形式是等价的。 中括号 [] 被称作下标运算符它的优先级高于一切其他运算符。通常的形式为
A[k]而表达式运算时最终会将下标运算符展开为
*(A k)测试一下 #include stdio.h
int main()
{int arr[5] { 111, 222, 333, 444, 555 };printf(arr[2] %d\n, arr[2]);printf(2[arr] %d\n, 2[arr]);printf(*(arr 2) %d\n, *(arr 2));return 0;
}arr[2] 展开为 *(arr 2) 。2[arr] 展开为 *(2 arr) 。
因此使用指针访问数组等价于下标访问。
指针作为参数传递
形参与实参相互独立
#include stdio.h
void swap(int x, int y)
{// 打印xy的首地址printf(x %u\n, x);printf(y %u\n, y);int temp x;x y;y temp;
}
int main()
{int a, b;int temp;a 1;b 2;// 打印ab的首地址printf(a %u\n, a);printf(b %u\n, b);// 交换ab变量swap(a, b);return 0;
}分析过程 {% gallery::::one %} {% endgallery %}
将指针作为参数传递
#include stdio.h
void swap(int* x, int* y)
{int temp *x;*x *y;*y temp;
}
int main()
{int a, b;int temp;a 1;b 2;printf(a%d b%d\n, a, b);// 交换ab变量swap(a, b);printf(a%d b%d\n, a, b);return 0;
}不是交换指针xy的值而是交换目标数据对象ab的值。所以需要在指针前使用取值运算符*
为何在使用 scanf 函数时需要对变量先取地址再传入参数
int n;
scanf(%d, n);scanf 会从读取从键盘的输入转换后存储到变量n当中。被调函数 scanf 无法直接修改在主调函数中的变量n。因此我们将变量n的指针传入 scanf 函数。通过指针使得被调函数间接地修改主调函数中的变量
指针不仅仅是首地址
再次强调指针内保存的不仅仅是目标数据对象首地址指针的类型也非常重要。 要在内存中找到一个数据对象需要有以下两个信息。
数据对象的首地址。数据对象占用存储空间大小。
指针的值保存着数据对象首地址指针类型对应着目标数据对象的类型用于标记目标数据对象的空间大小和指针运算时的步长。
仅有首地址的指针类型void *
由于指针类型定死了指针所指向的数据类型。为了让函数可以交换更多的数据类型我们仅需要指针类型中保存的首地址目标数据大小通过额外的参数传入。
不同指针类型不能相互赋值相互赋值后会造成目标数据对象类型的改变无法通过编译。void* 类型为特例它可以接受任意指针类型的赋值也可以赋值给任意类型的指针。
void swap(void* x, void* y, int size)
{// 指针转为char *单个字节操作内存char* pX (char*)x;char* pY (char*)y;char temp;for (int i 0; i size; i){temp pX[i];pX[i] pY[i];pY[i] temp;}
}由于 void _ 不能取值和加减所以我们将其转换为 char _ 。 char * 可以提供单个单个操作内存的能力。 在C语言中 void *类型不但可以接受任意类型的指针也可以自动转换为任意类型的指针。 但在C中规则稍微严格了一点 void * 仅能接受任意类型的指针不能自动转换为其他类型的指针。为了保证代码的兼容性我们将 void * 强制转为 char * 避免在C中编译出错。
char *pX (char *)x;
char *pY (char *)y;多级指针与指针数组
int * 的指针的类型为 int **
int **p; // 正确
int**p; // 正确
int* *p; // 正确
int * *p; // 正确
int * * p; // 正确二级指针为例 #include stdio.h
int main()
{int n 123;int* pn n;int** pnn pn;printf(**pnn %d\n, **pnn);return 0;
}取地址过程
对n使用取地址运算符获得n的指针pn类型为 int * 。对pn使用取地址运算符获得pn的指针pnn类型为 int ** 。
取值过程
对pnn使用取值运算符将 int ** 还原为 int * 。对_pnn使用取值运算符将 int _ 还原为 int 。即还原为n。
指针数组 p 指向 pToArr 的第一个元素类型为 int ** 。
*p 指向 arr1 的第一个元素类型为 int * 。
*p j 指向 arr1 中的第j个元素类型为 int * 。
*(*p j) 为 arr1 中的第j个元素。多维数组名与指针 数组指针的移动
#include stdio.h
int main()
{int b[5][10] {{0,1,2,3,4,5,6,7,8,9},{10,11,12,13,14,15,16,17,18,19},{20,21,22,23,24,25,26,27,28,29},{30,31,32,33,34,35,36,37,38,39},{40,41,42,43,44,45,46,47,48,49},};int(*pInt10)[10] b; // int[5][10]转为int (*)[10]int(*pInt) *pInt10; // *pInt10从int[10]转换为int *printf(pInt[0]%d\n, pInt[0]); // 等价于*(pInt 0)printf(pInt[1]%d\n, pInt[1]); // 等价于*(pInt 1)printf(pInt[2]%d\n, pInt[2]); // 等价于*(pInt 2)printf(pInt[3]%d\n, pInt[3]); // 等价于*(pInt 3)printf(pInt[4]%d\n, pInt[4]); // 等价于*(pInt 4)printf(pInt[5]%d\n, pInt[5]); // 等价于*(pInt 5)printf(pInt[6]%d\n, pInt[6]); // 等价于*(pInt 6)printf(pInt[7]%d\n, pInt[7]); // 等价于*(pInt 7)printf(pInt[8]%d\n, pInt[8]); // 等价于*(pInt 8)printf(pInt[9]%d\n, pInt[9]); // 等价于*(pInt 9)printf(pInt[10]%d\n, pInt[10]); // 等价于*(pInt 10)return 0;
}另一种结果相同的表达 #include stdio.h
int main()
{int b[5][10] {{0,1,2,3,4,5,6,7,8,9},{10,11,12,13,14,15,16,17,18,19},{20,21,22,23,24,25,26,27,28,29},{30,31,32,33,34,35,36,37,38,39},{40,41,42,43,44,45,46,47,48,49},};int(*pInt10)[10] b; // int[5][10]转为int (*)[10]printf(*pInt10[0]%d\n, (*pInt10)[0]); // pInt10先从int(*)[10]转为int *再通过下标访问printf(*pInt10[1]%d\n, (*pInt10)[1]);printf(*pInt10[2]%d\n, (*pInt10)[2]);printf(*pInt10[3]%d\n, (*pInt10)[3]);printf(*pInt10[4]%d\n, (*pInt10)[4]);printf(*pInt10[5]%d\n, (*pInt10)[5]);printf(*pInt10[6]%d\n, (*pInt10)[6]);printf(*pInt10[7]%d\n, (*pInt10)[7]);printf(*pInt10[8]%d\n, (*pInt10)[8]);printf(*pInt10[9]%d\n, (*pInt10)[9]);printf(*pInt10[10]%d, (*pInt10)[10]);return 0;
}输出结果 *pInt10[0]0
*pInt10[1]1
*pInt10[2]2
*pInt10[3]3
*pInt10[4]4
*pInt10[5]5
*pInt10[6]6
*pInt10[7]7
*pInt10[8]8
*pInt10[9]9
*pInt10[10]10由于下标运算符[]的优先级比取值运算符*的优先级高。我们想要pInt10先从int (*)[10]转为int *再通过下标访问。所以需要用括号让取值先进行。
如果数组名B出现在表达式中会从int[5][10]转为int (*)[10]。 *B又可以看作*(B 0)所以*B等价于B[0]。
对数组取地址
当数组名arr出现在一个表达式当中数组名arr将会被转换为指向数组首元素的指针。但是这个规则有两个例外
对数组名arr使用sizeof时。对数组名arr使用时。
现在开始讨论第二个例外。
int arr[10];
arr;arr的类型为int[10]而对数组使用取地址运算符。触发第二个例外数组不会进行类型转换而是直接取地址。
对int取地址为int (*)类型的指针。对int[10]取地址为int (*)[10]类型的指针。
#include stdio.h
int main()
{int arr[10];int(*pInt10)[10] arr;printf(pInt10%u\n, pInt10);printf(pInt101%u\n, pInt10 1);return 0;
}pInt10是类型int (*)[10]类型的数组指针步长为40。 pInt107601480
pInt1017601520如果再对pInt10取地址呢 对int[10]类型的数组取地址为int (*)[10]类型的数组指针它指向int[10]的数组。对int (*)[10]类型的数组指针取地址为int (**)[10]的二级指针它指向int(*)[10]的指针。
注意arr是不对的arr确实可以获得一个指针。但是这个指针是一个临时数据对象应当将其赋值给变量才能保存它的值。
指针与三维数组示例
#include stdio.h
int main()
{int S[2][5][10] {{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},{20, 21, 22, 23, 24, 25, 26, 27, 28, 29},{30, 31, 32, 33, 34, 35, 36, 37, 38, 39},{40, 41, 42, 43, 44, 45, 46, 47, 48, 49}},{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},{10, 11, 12, 13, 14, 15, 16, 17, 18, 19},{20, 21, 22, 23, 24, 25, 26, 27, 28, 29},{30, 31, 32, 33, 34, 35, 36, 37, 38, 39},{40, 41, 42, 43, 44, 45, 46, 47, 48, 49}}};// 访问元素S[1][2][3]printf(S[1][2][3] %d, *(*(*(S 1) 2) 3));return 0;
}分析
S 1 类型为 int(*)[5][10] 的指针。
*(S 1) 类型为 int[5][10] 的数组。
*(S 1) 2 类型为 int(*)[10] 的指针。
*(*(S 1) 2) 类型为 int[10] 的数组。
*(*(S 1) 2) 3 类型为 int(*) 的指针。
*(*(*(S 1) 2) 3) 类型为 int 的整型。{% gallery::::one %} {% endgallery %} 也可以故意将表达式结果赋值给一个无法转换的变量。让报错信息告诉我们表达式结果具体的类型 验证 指针的大小为4整型的大小为4。 int[5][10] 数组的大小为200。 int[10] 数组的大小为40。
对数组取地址
对 int[2][5][10] 取地址为 int (*)[2][5][10] 类型的指针。
多级指针应用
从函数中返回指针
return关键词可以从被调函数中返回一个值到主调函数。 现在我们尝试让它返回一个指针到主调函数中。
#include stdio.h
int* func()
{int n 100;return n;
}
int main()
{int* p func();printf(%d\n, *p);return 0;
}我们在函数 func 中定义了变量n。接着return n;取得变量n的指针并返回到main函数。 main函数收到返回值后赋值给p并使用指针p来访问变量n。
这个程序看似正确并且可以通过编译。但是却存在潜在问题。 这是因为函数结束后函数内部的变量也会被回收。所以变量 n 已经失效了。再去访问它有可能正常也有可能得到一些无意义的值或者引发错误。 这样设计的原因是因为函数与函数之间的变量是独立的即使是同一个函数多次运行这些变量也是独立的。在函数返回后函数内的变量没有继续存在的意义了。所以函数内的变量将被回收回收后的内存空间将给接下来运行的函数使用。 如果不想让变量被回收那么可以在变量前加上关键词**static**
int* func()
{static int n 100; // 关键词static让变量n不被回收n; // 变量n自增return n;
}
#include stdio.h
int main()
{int* p func();printf(%d\n, *p);func();printf(%d\n, *p);func();printf(%d\n, *p);func();printf(%d\n, *p);func();printf(%d\n, *p);return 0;
}现在函数 func 结束后变量n不会被回收了。并且重复调用func函数使用的是同一个地址上的变量n。 因此我们只需获取一次变量n的地址即可观察到变量n每次调用函数都被自增。
从函数中返回多个变量
将指针的指针也就是二级指针作为参数传入函数。即可让被调函数“返回”多个指针。
void func(int** a, int** b)
{static int x 100;static int y 200;*a x;*b y;
}
#include stdio.h
int main()
{// 两个指针初始化为空int* a NULL;int* b NULL;func(a, b); // 将指针的指针传入被调函数if (a ! NULL b ! NULL)printf(a%d b%d\n, *a, *b);return 0;
}在main函数中声明两个指针并把它们初始化为 NULL 。 **NULL**** 是一个由 **#define NULL 0** 定义的符号常量。** 将指针初始化为NULL也就是将指针内保存的地址设置为0。 让指针初始化为零是一个非常好的编码习惯。 一般结合指针判空即 if (a ! NULL b ! NULL) 来判断指针是不是有一个正确的指向了。 调用函数 func 后两个指针均被修改为有效指针即非0。 我们通过判断指针是不是非零来确定函数 func 已经给指针赋值了。 若指针仍然为0则说明函数 func 并未给指针赋值不可以使用没有明确指向的指针。 函数 func 内部 x 、 y 取得变量 x 、 y 的指针类型为 int * 。 在被调函数内为了修改主调函数中的变量先对二级指针 a、b 取值将 int ** 转换为 int * 再赋 值一个 int 给它。 类似于使用一级指针作为参数时先对一级指针 a、b 取值将 int * 转换为 int 再赋值一个 int 给它。