2022-11-16指针操作

指针操作

指针变量的 8种基本操作

赋值: 可以把地址赋给指针。用数组名、 带地址运算符(&) 的变量名、 另一个指针进行赋值。

解引用: *运算符给出指针指向地址上储存的值。

取址: 和所有变量一样, 指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址。

指针与整数相加: 可以使用+运算符把指针与整数相加, 或整数与指针相加。无论哪种情况, 整数都会和指针所指向类型的大小(以字节为单位)相乘, 然后把结果与初始地址相加。

递增指针: 递增指向数组元素的指针可以让该指针移动至数组的下一个元素。ptr1++相当于把ptr1的值加上4(我们的系统中int为4字节) ,ptr1指向urn[1]

指针减去一个整数: 可以使用-运算符从一个指针中减去一个整数。 指针必须是第1个运算对象, 整数是第 2 个运算对象。 该整数将乘以指针指向类型的大小(以字节为单位) , 然后用初始地址减去乘积。

递减指针: 当然, 除了递增指针还可以递减指针。

指针求差: 可以计算两个指针的差值。 通常, 求差的两个指针分别指向同一个数组的不同元素, 通过计算求出两元素之间的距离。 差值的单位与数组类型的单位相同。

使用关系运算符可以比较两个指针的值, 前提是两个指针都指向相同类型的对象。

这里的减法有两种。 可以用一个指针减去另一个指针得到一个整数, 或者用一个指针减去一个整数得到另一个指针。

编译器不会检查指针是否仍指向数组元素。 C 只能保证指向数组任意元素的指针和指向数组后面第 1 个位置的指针有效。 但是, 如果递增或递减一个指针后超出了这个范围, 则是未定义的。 另外, 可以解引用指向数组任意元素的指针。 但是, 即使指针指向数组后面一个位置是有效的, 也能解引用这样的越界指针。

创建一个指针时, 系统只分配了储存指针本身的内存, 并未分配储存数据的内存。 因此,在使用指针之前, 必须先用已分配的地址初始化它。

保护数组中的数据

传递int类型的值还是传递指向int的指针。 通常都是直接传递数值, 只有程序需要在函数中改变该数值时, 才会传递指针。如果一个函数按值传递数组, 则必须分配足够的空间来储存原数组的副本, 然后把原数组所有的数据拷贝至新的数组中。 如果把数组的地址传递给函数, 让函数直接处理原数组则效率要高。

处理数组的函数通常都需要使用原始数据, 因此这样的函数可以修改原数组。

add_to(prices, 100, 2.50);

该函数修改了数组中的数据。函数通过指针直接使用了原始数据。

对形式参数使用const

如果函数的意图不是修改数组中的数据内容, 那么在函数原型和函数定义中声明形式参数时应使用关键字const。

int sum(const int ar[], int n); /* 函数原型 */

int sum(const int ar[], int n) /* 函数定义 */

int i;

int total = 0;

for( i = 0; i < n; i++)

total += ar[i];

return total;

}

const告诉编译器, 该函数不能修改ar指向的数组中的内容。 如果在函数中不小心使用类似ar[i]++的表达式, 编译器会捕获这个错误, 并生成一条错误信息。

这样使用const并不是要求原数组是常量, 而是该函数在处理数组时将其视为常量, 不可更改。使用const可以保护数组的数据不被修改, 就像按值传递可以保护基本数据类型的原始值不被改变一样。

一个函数显示数组的内容, 另一个函数给数组每个元素都乘以一个给定值。 因为第1个函数不用改变数组, 所以在声明数组形参时使用了const; 而第2个函数需要修改数组元素的值, 所以不使用const。

comst的其他内容

const double PI = 3.14159;

#define指令可以创建类似功能的符号常量, 但是const的用法更加灵活。 可以创建const数组、 const指针和指向const的指针。

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};

const double * pd = rates; // pd指向数组的首元素

第2行代码把pd指向的double类型的值声明为const, 这表明不能使用pd来更改它所指向的值

*pd = 29.89; // 不允许

pd[2] = 222.22; //不允许

rates[0] = 99.99; // 允许, 因为rates未被const限定

无论是使用指针表示法还是数组表示法, 都不允许使用pd修改它所指向数据的值。 但是要注意, 因为rates并未被声明为const, 所以仍然可以通过rates修改元素的值。

指向 const 的指针通常用于函数形参中, 表明该函数不会使用指针改变数据。

关于指针赋值和const需要注意一些规则。 首先, 把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};

const double locked[4] = {0.08, 0.075, 0.0725, 0.07};

const double * pc = rates; // 有效

pc = locked; //有效

pc = &rates[3]; //有效

 只能把非const数据的地址赋给普通指针

show_array()函数可以接受普通数组名和 const数组名作为参数, 因为这两种参数都可以用来初始化指向const的指针

show_array(rates, 5); // 有效

show_array(locked, 4); // 有效

对函数的形参使用const不仅能保护数据, 还能让函数处理const数组。

C标准规定, 使用非const标识符(如, mult_arry()的形参ar) 修改const数据(如, locked) 导致的结果是未定义的。

const还有其他的用法。 例如, 可以声明并初始化一个不能指向别处的指针, 关键是const的位置

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};

double * const pc = rates; // pc指向数组的开始

pc = &rates[2]; // 不允许, 因为该指针不能指向别处

*pc = 92.99; // 没问题 -- 更改rates[0]的值

用这种指针修改它所指向的值, 但是它只能指向初始化时设置的地址。

在创建指针时还可以使用const两次, 该指针既不能更改它所指向的地址, 也不能修改指向地址上的值。

指针和多维数组

int zippo[4][2]; /* 内含int数组的数组 */

数组名zippo是该数组首元素的地址。 在本例中, zippo的首元素是一个内含两个int值的数组, 所以zippo是这个内含两个int值的数组的地址。

zippo是数组首元素的地址, 所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组, 所以zippo[0]的值和它首元素(一个整数) 的地址(即&zippo[0][0]的值) 相同。

给指针或地址加1, 其值会增加对应类型大小的数值。

解引用一个指针(在指针前使用*运算符) 或在数组名后使用带下标的[]运算符, 得到引用对象代表的值。

zippo是地址的地址, 必须解引用两次才能获得原始值。 地址的地址或指针的指针是就是双重间接(double indirection) 的例子。

该输出显示了二维数组zippo的地址和一维数组zippo[0]的地址相同。 它们的地址都是各自数组首元素的地址, 因而与&zippo[0][0]的值也相同。

对二维数组名解引用两次, 得到储存在数组中的值。 使用两个间接运算符(*) 或者使用两对方括号([]) 都能获得该值(还可以使用一个*和一对[], 但是我们暂不讨论这么多情况) 。

如果程序恰巧使用一个指向二维数组的指针, 而且要通过该指针获取值时, 最好用简单的数组表示法, 而不是指针表示法。

指向多维数组的指针

int (* pz)[2]; // pz指向一个内含两个int类型值的数组

把pz声明为指向一个数组的指针, 该数组内含两个int类型值。[]的优先级高于*

int * pax[2]; // pax是一个内含两个指针元素的数组, 每个元素都指向int的指针。

由于[]优先级高, 先与pax结合, 所以pax成为一个内含两个元素的数组。 然后*表示pax数组内含两个指针。这行代码声明了两个指向int的指针。 而前面有圆括号, *先与pz结合, 因此声明的是一个指向数组(内含两个int类型的值) 的指针。

pz是一个指针, 不是数组名, 但是也可以使用 pz[2][1]这样的写法。 可以用数组表示法或指针表示法来表示一个数组元素, 既可以使用数组名, 也可以使用指针名。

指针的兼容性

指针之间的赋值比数值类型之间的赋值要严格。编译器在编译代码时, 可能会给出警告, 执行这样的代码是未定义的。

const int **pp2;

int *p1;

const int n = 13;

pp2 = &p1; // 允许, 但是这导致const限定符失效(根据第1行代码,不能通过*pp2修改它所指向的内容)

*pp2 = &n; // 有效, 两者都声明为const, 但是这将导致p1指向n(*pp2已被修改)

*p1 = 10;//有效, 但是这将改变n的值(但是根据第3行代码, 不能修改n的值)

标准规定了通过非const指针更改const数据是未定义的。

C const和C++ const

C和C++中const的用法很相似, 但是并不完全相同。 区别之一是,C++允许在声明数组大小时使用const整数, 而C却不允许。 区别之二是,C++的指针赋值检查更严格

const int y;

const int * p2 = &y;

int * p1;

p1 = p2; // C++中不允许这样做, 但是C可能只给出警告

C++不允许把const指针赋给非const指针。 而C则允许这样做, 但是如果通过p1更改y, 其行为是未定义的。

函数和多维数组

如果要编写处理二维数组的函数, 首先要能正确地理解指针才能写出声明函数的形参。

void somefunction( int pt[][4] );

第1个方括号是空的。 空的方括号表明pt是一个指针。 这样的变量稍后可以用作相同方法作为junk。

程序把数组名junk(即, 指向数组首元素的指针, 首元素是子数组) 和符号常量ROWS(代表行数3) 作为参数传递给函数。 每个函数都把ar视为内含数组元素(每个元素是内含4个int类型值的数组) 的数组。 列数内置在函数体中, 但是行数靠函数传递得到。

ar和main()中的junk都使用数组表示法。 因为ar和junk的类型相同, 它们都是指向内含4个int类型值的数组的指针。

编译器会把数组表示法转换成指针表示法。int sum2(int ar[][4], int rows); // 有效声明。

表示ar指向一个内含4个int类型值的数组(在我们的系统中, ar指向的对象占16字节) , 所以ar+1的意思是“该地址加上16字节”。

变长数组

变长数组有一些限制。 变长数组必须是自动存储类别, 这意味着无论在函数中声明还是作为函数形参声明, 都不能使用static或extern存储类别说明符。而且, 不能在声明中初始化它们。 最终,C11把变长数组作为一个可选特性, 而不是必须强制实现的特性。

变长数组不能改变大小

变长数组中的“变”不是指可以修改已创建数组的大小。 一旦创建了变长数组, 它的大小则保持不变。 这里的“变”指的是: 在创建数组时, 可以使用变量指定数组的维度。

在函数定义的形参列表中声明的变长数组并未实际创建数组。 和传统的语法类似, 变长数组名实际上是一个指针。 这说明带变长数组形参的函数实际上是在原始数组中处理数组, 因此可以修改传入的数组。

数组的大小必须是给定的整型常量表达式, 可以是整型常量组合,C实现可以扩大整型常量表达式的范围, 所以可能会允许使用const, 但是这种代码可能无法移植。

C99/C11 标准允许在声明变长数组时使用 const 变量。 所以该数组的定义必须是声明在块中的自动存储类别数组。

变长数组还允许动态内存分配, 这说明可以在程序运行时指定数组的大小。 普通 C数组都是静态内存分配, 即在编译时确定数组的大小。

复合字面量

字面量是除符号常量外的常量。对于数组, 复合字面量类似数组初始化列表, 前面是用括号括起来的类型名。

初始化有数组名的数组时可以省略数组大小, 复合字面量也可以省略大小, 编译器会自动计算数组当前的元素个数。

因为复合字面量是匿名的, 所以不能先创建然后再使用它, 必须在创建的同时使用它。 使用指针记录地址就是一种用法。

复合字面量的类型名也代表首元素的地址, 所以可以把它赋给指向int的指针。

复合字面量是提供只临时需要的值的一种手段。 复合字面量具有块作用域这意味着一旦离开定义复合字面量的块, 程序将无法保证该字面量是否存在。 也就是说, 复合字面量的定义在最内层的花括号中。

关键概念

数组用于储存相同类型的数据。 C 把数组看作是派生类型, 因为数组是建立在其他类型的基础上。 也就是说, 无法简单地声明一个数组。 在声明数组时必须说明其元素的类型, 如int类型的数组、 float类型的数组, 或其他类型的数组。 所谓的其他类型也可以是数组类型, 这种情况下, 创建的是数组的数组(或称为二维数组) 。

在把数组名作为实际参数时, 传递给函数的不是整个数组, 而是数组的地址(因此, 函数对应的形式参数是指针) 。数组地址提供了“地址”, “元素个数”可以内置在函数中或作为单独的参数传递。

数组和指针的关系密切, 同一个操作可以用数组表示法或指针表示法。它们之间的关系允许你在处理数组的函数中使用数组表示法, 即使函数的形式参数是一个指针, 而不是数组。

本章小结

数组是一组数据类型相同的元素。 数组元素按顺序储存在内存中, 通过整数下标(或索引) 可以访问各元素。 在C中, 数组首元素的下标是0, 所以对于内含n个元素的数组, 其最后一个元素的下标是n-1。

C把数组名解释为该数组首元素的地址。 换言之, 数组名与指向该数组首元素的指针等价。 概括地说, 数组和指针的关系十分密切。

C 语言, 不能把整个数组作为参数传递给函数, 但是可以传递数组的地址。 然后函数可以使用传入的地址操控原始数组。 如果函数没有

修改原始数组的意图, 应在声明函数的形式参数时使用关键字const。 在被调函数中可以使用数组表示法或指针表示法, 无论用哪种表示法, 实际上使用的都是指针变量。

指针加上一个整数或递增指针, 指针的值以所指向对象的大小为单位改变。

C 语言传递多维数组的传统方法是把数组名(即数组的地址) 传递给类型匹配的指针形参。 声明这样的指针形参要指定所有的数组维度, 除了第1个维度。 传递的第1个维度通常作为第2个参数。

变长数组提供第2种语法, 把数组维度作为参数传递。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 1.指针到底是什么? 1.1、指针变量和普通变量的区别 首先必须非常明确:指针的实质就是个变量,它跟普通变量没有任...
    嵌入式Linux小白阅读 106评论 0 0
  • 1. 数组 数组声明 数组初始化 使用const声明数组,数组为只读,不能再修改。 生命数组后如果未进行初始化,数...
    JAVA孙剑阅读 286评论 0 1
  • 1.c语言中,有时需要使用只读数组,也就是程序从数组中读取数值,但是程序不向数组中写数据,在这种情况下声明并初始化...
    田亢阅读 841评论 0 0
  • 1 指针数组 指针数组的每个元素都是地址int *p[2];由p[0] 和p[1]两个指针组成 指针数组和二维数组...
    华大李慢慢阅读 205评论 0 0
  • 数组 数组部分初始化,剩余元素都会被初始化为0int days[12]={31,28,[4]=31,30,31,[...
    SkrGiaooo阅读 232评论 0 0