[Objective-C语言教程]指针(15)
每个变量都是一个内存位置,每个内存位置都定义了它的地址,可以使用符号(&
)运算符进行访问,该运算符表示内存中的地址。
考虑以下示例,它将打印定义的变量的地址 -
1 #import <Foundation/Foundation.h> 2 3 int main () { 4 int var1; 5 char var2[10]; 6 7 NSLog(@"Address of var1 variable: %x\n", &var1 ); 8 NSLog(@"Address of var2 variable: %x\n", &var2 ); 9 10 return 0; 11 }
执行上面示例代码,得到以下结果:
1 2018-11-15 03:56:08.348 main[108988] Address of var1 variable: fe8568c0 2 2018-11-15 03:56:08.351 main[108988] Address of var2 variable: fe8568c6
通过上面代码,了解了什么是内存地址以及如何访问它,到此,概念的基础知识已经结束。 接下来看看什么是指针。
1. 指针是什么?
指针是一个变量,它的值是另一个变量的地址,即存储单元的直接地址。 与任何变量或常量一样,必须先声明指针,然后才能使用它来存储任何变量地址。 指针变量声明的一般形式是 -
type *var_name;
这里,type
是指针的基类型; 它必须是有效的Objective-C数据类型,var_name
是指针变量的名称。 用于声明指针的星号*
与用于乘法的星号相同。 但是,在此语句中,星号用于将变量指定为指针。以下是有效的指针声明 -
1 int *ip; /* 指向 int 类型的指针 */ 2 double *dp; /* 指向 double 类型的指针 */ 3 float *fp; /* 指向 float 类型的指针 */ 4 char *ch /* 指向 char 类型的指针 */
所有指针的值是实际数据类型的地址值,无论是整数,浮点数,字符还是其他,都是相同的,是表示内存地址的长十六进制数。 不同数据类型的指针之间的唯一区别是指针指向的变量或常量的数据类型。
2. 如何使用指针?
有一些重要的操作,经常在指针的帮助下完成。使用指针的步骤如下 -
- 定义一个指针变量,
- 将变量的地址赋给指针,
- 最后访问指针变量中可用地址的值。
这是通过使用一元运算符*
来完成的,该运算符*
返回位于操作数指定的地址处的变量值。以下示例使用这些操作 -
1 #import <Foundation/Foundation.h> 2 3 int main () { 4 int var = 20; /* 变量定义 */ 5 int *ip; /* 指针变量声明 */ 6 ip = &var; /* 在指针变量中存储 var 的地址*/ 7 8 NSLog(@"Address of var variable: %x\n", &var ); 9 10 /* 存储在指针变量中的地址 */ 11 NSLog(@"Address stored in ip variable: %x\n", ip ); 12 13 /* 使用指针访问该值 */ 14 NSLog(@"Value of *ip variable: %d\n", *ip ); 15 16 return 0; 17 }
执行上面示例代码,得到以下结果:
1 2018-11-15 04:05:36.179 main[80041] Address of var variable: 23bea2dc 2 2018-11-15 04:05:36.183 main[80041] Address stored in ip variable: 23bea2dc 3 2018-11-15 04:05:36.183 main[80041] Value of *ip variable: 20
3. NULL指针
如果没有要分配的确切地址,最好将NULL
值分配给指针变量。这是在变量声明时完成的。 指定为NULL
的指针称为空指针。
NULL
指针是一个常量,在几个标准库中定义了零值。参考以下程序 -
1 #import <Foundation/Foundation.h> 2 3 int main () { 4 int *ptr = NULL; 5 NSLog(@"The value of ptr is : %x\n", ptr ); 6 return 0; 7 }
执行上面示例代码,得到以下结果:
2018-11-15 04:26:24.203 main[40259] The value of ptr is : 0
在大多数操作系统上,程序不允许访问地址0
处的内存,因为该内存是由操作系统保留的。 但是,存储器地址0
具有特殊意义; 它表示指针不是指向可访问的内存位置。 但按照惯例,如果指针包含null
(零)值,则假定它不指向内容。
要检查空指针,可以使用if
语句,如下所示 -
1 if(ptr) /* 如果p不为null,则成立 */ 2 if(!ptr) /* 如果p为null,则成立 */
4. 指针详解
指针有许多但很简单的概念,它们对Objective-C编程非常重要。以下几个重要的指针概念,对于Objective-C程序员来说应该要清楚 -
编号 | 概念 | 描述 |
---|---|---|
1 | Objective-C指针运算 | 在指针上使用四个算术运算符:++ ,-- ,+ ,- |
2 | Objective-C指针数组 | 可以定义数组以包含许多指针 |
3 | Objective-C指针的指针 | Objective-C允许有指向指针的指针 |
4 | Objective-C将指针传递给函数 | 通过引用或地址传递参数都可以在调用函数中更改传递的参数。 |
5 | Objective-C从函数返回指针 | Objective-C允许函数返回指向局部变量,静态变量和动态分配内存的指针。 |
4.1 指针运算
正如上篇章节中所解释的那样,Objective-C指针是一个地址,它是一个数值。 因此,可以像对数值一样对指针执行算术运算。 可以在指针上使用四个算术运算符:++
,--
,+
和 -
要理解指针运算,假设ptr
是一个整数指针,它指向地址1000
。假设32
位整数,对指针执行以下算术运算 -
ptr++
现在,在上述操作之后,ptr
将指向位置1004
,因为每次ptr
递增时,它将指向下一个整数位置,它是当前位置之后的4
个字节。 此操作将指针移动到下一个存储器位置,而不会影响存储器位置的实际值。 如果ptr
指向地址为1000
的字符,则上述操作将指向位置1001
,因为下一个字符在1001
处可用。
4.11 递增指针
在程序中使用指针比数组方便,因为变量指针可以递增,不像数组名称,它只是一个常量指针,所以不能递增。 以下程序增加变量指针以访问数组的每个后续元素 -
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 3; 4 5 int main () { 6 int var[] = {10, 100, 200}; 7 int i, *ptr; 8 9 /* 在指针中存储数组地址*/ 10 ptr = var; 11 for ( i = 0; i < MAX; i++) { 12 NSLog(@"Address of var[%d] = %x\n", i, ptr ); 13 NSLog(@"Value of var[%d] = %d\n", i, *ptr ); 14 15 /* 移动到下一个位置 */ 16 ptr++; 17 } 18 return 0; 19 }
执行上面示例代码,得到以下结果:
1 2018-11-15 05:50:37.956 main[195398] Address of var[0] = 518e55a4 2 2018-11-15 05:50:37.958 main[195398] Value of var[0] = 10 3 2018-11-15 05:50:37.958 main[195398] Address of var[1] = 518e55a8 4 2018-11-15 05:50:37.958 main[195398] Value of var[1] = 100 5 2018-11-15 05:50:37.958 main[195398] Address of var[2] = 518e55ac 6 2018-11-15 05:50:37.958 main[195398] Value of var[2] = 200
4.12 递减指针
递减指针同样也适用,该指针减少其数据类型的字节数,如下所示 -
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 3; 4 5 int main () { 6 int var[] = {10, 100, 200}; 7 int i, *ptr; 8 9 /* 在指针中存储数组地址 */ 10 ptr = &var[MAX-1]; 11 for ( i = MAX; i > 0; i--) { 12 NSLog(@"Address of var[%d] = %x\n", i, ptr ); 13 NSLog(@"Value of var[%d] = %d\n", i, *ptr ); 14 15 /* 移动到上一个位置 */ 16 ptr--; 17 } 18 return 0; 19 }
执行上面示例代码,得到以下结果:
1 2018-11-15 05:53:29.272 main[63838] Address of var[3] = 3667141c 2 2018-11-15 05:53:29.274 main[63838] Value of var[3] = 200 3 2018-11-15 05:53:29.274 main[63838] Address of var[2] = 36671418 4 2018-11-15 05:53:29.274 main[63838] Value of var[2] = 100 5 2018-11-15 05:53:29.274 main[63838] Address of var[1] = 36671414 6 2018-11-15 05:53:29.274 main[63838] Value of var[1] = 10
4.13. 指针比较
可使用关系运算符比较指针,例如==
,<
和>
。 如果p1
和p2
指向彼此相关的变量,例如同一数组的元素,则可以有意义地比较指针:p1
和p2
。
以下程序通过递增变量指针来修改前一个示例,只要它指向的地址小于或等于数组的最后一个元素的地址,即&var [MAX - 1]
-
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 3; 4 5 int main () { 6 int var[] = {10, 100, 200}; 7 int i, *ptr; 8 9 /* 获取到指针中第一个元素的地址*/ 10 ptr = var; 11 i = 0; 12 13 while ( ptr <= &var[MAX - 1] ) { 14 NSLog(@"Address of var[%d] = %x\n", i, ptr ); 15 NSLog(@"Value of var[%d] = %d\n", i, *ptr ); 16 17 /* 指向下一个位置 */ 18 ptr++; 19 i++; 20 } 21 return 0; 22 }
执行上面示例代码,得到以下结果:
1 2018-11-15 05:55:48.173 main[60442] Address of var[0] = 6a8cb7f4 2 2018-11-15 05:55:48.175 main[60442] Value of var[0] = 10 3 2018-11-15 05:55:48.175 main[60442] Address of var[1] = 6a8cb7f8 4 2018-11-15 05:55:48.175 main[60442] Value of var[1] = 100 5 2018-11-15 05:55:48.175 main[60442] Address of var[2] = 6a8cb7fc 6 2018-11-15 05:55:48.175 main[60442] Value of var[2] = 200
4.2 指针数组
在理解指针数组的概念之前,先来看看以下示例,在这个示例中使用3
个整数的数组 -
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 3; 4 5 int main () { 6 int var[] = {10, 100, 200}; 7 int i; 8 9 for (i = 0; i < MAX; i++) { 10 NSLog(@"Value of var[%d] = %d\n", i, var[i] ); 11 } 12 return 0; 13 }
执行上面示例代码,得到以下结果:
1 2018-11-15 05:57:51.168 main[13412] Value of var[0] = 10 2 2018-11-15 05:57:51.170 main[13412] Value of var[1] = 100 3 2018-11-15 05:57:51.170 main[13412] Value of var[2] = 200
如果想要维护一个数组,该数组可以存储指向int
或char
或任何其他可用数据类型的指针。 以下是一个指向整数的指针数组的声明 -
int *ptr[MAX];
这将ptr
声明为MAX
整数指针的数组。 因此,ptr
中的每个元素现在都包含一个指向int
类型值的指针。 下面的例子使用了三个整数,它们将存储在一个指针数组中,如下所示 -
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 3; 4 5 int main () { 6 int var[] = {10, 100, 200}; 7 int i, *ptr[MAX]; 8 9 for ( i = 0; i < MAX; i++) { 10 ptr[i] = &var[i]; /* 分配整数的地址. */ 11 } 12 for ( i = 0; i < MAX; i++) { 13 NSLog(@"Value of var[%d] = %d\n", i, *ptr[i] ); 14 } 15 return 0; 16 }
执行上面示例代码,得到以下结果 -
1 2018-11-15 06:00:41.868 main[84495] Value of var[0] = 10 2 2018-11-15 06:00:41.870 main[84495] Value of var[1] = 100 3 2018-11-15 06:00:41.870 main[84495] Value of var[2] = 200
还可以使用指向字符的指针数组来存储字符串列表,如下所示 -
1 #import <Foundation/Foundation.h> 2 3 const int MAX = 4; 4 5 int main () { 6 char *names[] = {"Yiibai Su", "Hina Lee", "XNew Luang", "Kaops Ali",}; 7 int i = 0; 8 for ( i = 0; i < MAX; i++) { 9 NSLog(@"Value of names[%d] = %s\n", i, names[i] ); 10 } 11 12 return 0; 13 }
执行上面示例代码,得到以下结果:
1 2018-11-15 06:02:10.526 main[126578] Value of names[0] = Yiibai Su 2 2018-11-15 06:02:10.528 main[126578] Value of names[1] = Hina Lee 3 2018-11-15 06:02:10.528 main[126578] Value of names[2] = XNew Luang 4 2018-11-15 06:02:10.528 main[126578] Value of names[3] = Kaops Ali
4.3 指针的指针
指向指针的指针是多个间接或指针链的形式。 通常,指针包含变量的地址。 当定义指向指针的指针时,第一个指针包含第二个指针的地址,它指向包含实际值的位置,如下所示。
必须声明一个指向指针的指针的变量。 这是通过在名称前面放置一个额外的星号(*
)来完成的。 例如,以下是声明指向int
类型指针的指针的声明 -
int **var;
当目标值由指针指向间接指向时,访问该值需要使用两个星号(**
)运算符,如下例所示 -
1 #import <Foundation/Foundation.h> 2 3 int main () { 4 int var; 5 int *ptr; 6 int **pptr; 7 8 var = 250; 9 10 /* 取 var 变量的地址 */ 11 ptr = &var; 12 13 /* 使用运算符 & 地址获取 ptr的地址 */ 14 pptr = &ptr; 15 16 /* 使用 pptr取值 */ 17 NSLog(@"Value of var = %d\n", var ); 18 NSLog(@"Value available at *ptr = %d\n", *ptr ); 19 NSLog(@"Value available at **pptr = %d\n", **pptr); 20 21 return 0; 22 }
执行上面示例代码,得到以下结果:
1 2018-11-15 06:16:20.652 main[137834] Value of var = 250 2 2018-11-15 06:16:20.655 main[137834] Value available at *ptr = 250 3 2018-11-15 06:16:20.655 main[137834] Value available at **pptr = 250
4.4 将指针传递给函数
Objective-C编程语言允许传递指针作为参数给函数。为此,只需将函数的参数声明为指针类型即可。
下面这个例子中,将一个unsigned long
指针传递给一个函数并在函数内部的更改参数的值,它反映在调用的函数中 -
1 #import <Foundation/Foundation.h> 2 3 @interface SampleClass:NSObject 4 - (void) getSeconds:(int *)par; 5 @end 6 7 @implementation SampleClass 8 9 - (void) getSeconds:(int *)par { 10 /* 获取当前秒数 */ 11 //*par = time( NULL ); 12 *par = 111; 13 return; 14 } 15 16 @end 17 18 int main () { 19 20 int sec=100; 21 22 SampleClass *sampleClass = [[SampleClass alloc]init]; 23 [sampleClass getSeconds:&sec]; 24 25 /* 打印实际的值 */ 26 NSLog(@"Number of seconds: %d\n", sec ); 27 28 return 0; 29 }
执行上面示例代码,得到以下结果:
2018-11-15 06:23:36.731 main[14937] Number of seconds: 111
函数可以接受指针,也可以接受一个数组,如下例所示 -
1 #import <Foundation/Foundation.h> 2 3 @interface SampleClass:NSObject 4 /* function declaration */ 5 - (double) getAverage:(int *)arr ofSize:(int) size; 6 @end 7 8 @implementation SampleClass 9 10 - (double) getAverage:(int *)arr ofSize:(int) size { 11 int i, sum = 0; 12 double avg; 13 14 for (i = 0; i < size; ++i) { 15 sum += arr[i]; 16 } 17 18 avg = (double)sum / size; 19 return avg; 20 } 21 22 @end 23 24 int main () { 25 26 /* 定义一个有 5 个元素的数组 */ 27 int balance[5] = {99, 92, 93, 87, 90}; 28 double avg; 29 30 SampleClass *sampleClass = [[SampleClass alloc]init]; 31 /* 将指针传递给数组作为参数 */ 32 avg = [sampleClass getAverage: balance ofSize: 5 ] ; 33 34 /* 输出函数返回结果值 */ 35 NSLog(@"Average value is: %f\n", avg ); 36 37 return 0; 38 }
执行上面示例代码,得到以下结果:
2018-11-15 06:26:49.117 main[134265] Average value is: 92.200000
4.5 从函数返回指针
在上一节中,学习了Objective-C编程语言如何从函数返回数组。类似的,Objective-C也允许从函数返回指针。为此,必须声明一个返回指针的函数,如下例所示 -
1 int * myFunction() { 2 . 3 . 4 . 5 }
要记住的第二点是,将局部变量的地址返回到函数外部并不是一个做法,因此需要将局部变量定义为静态变量。
现在,考虑以下函数,它将生成10
个随机数并使用数组名称返回它们,该数组名称表示指针,即第一个数组元素的地址。
1 #import <Foundation/Foundation.h> 2 3 /* 用于生成和返回随机数的函数 */ 4 int * getRandom( ) { 5 static int r[10]; 6 int i; 7 8 /* 设置随机数的种子 */ 9 srand( (unsigned)time( NULL ) ); 10 for ( i = 0; i < 10; ++i) { 11 r[i] = rand(); 12 NSLog(@"%d\n", r[i] ); 13 } 14 15 return r; 16 } 17 18 /* 调用上面定义的函数的主函数 */ 19 int main () { 20 21 /* 一个指向 int 的指针 */ 22 int *p; 23 int i; 24 25 p = getRandom(); 26 for ( i = 0; i < 10; i++ ) { 27 NSLog(@"*(p + [%d]) : %d\n", i, *(p + i) ); 28 } 29 30 return 0; 31 }
执行上面示例代码,得到以下结果:
1 2018-11-15 06:32:31.948 main[61953] 1167938093 2 2018-11-15 06:32:31.950 main[61953] 1512817253 3 2018-11-15 06:32:31.950 main[61953] 1354576836 4 2018-11-15 06:32:31.950 main[61953] 1468996356 5 2018-11-15 06:32:31.950 main[61953] 965738374 6 2018-11-15 06:32:31.950 main[61953] 92597726 7 2018-11-15 06:32:31.950 main[61953] 206939477 8 2018-11-15 06:32:31.950 main[61953] 1164748971 9 2018-11-15 06:32:31.950 main[61953] 1551275529 10 2018-11-15 06:32:31.950 main[61953] 2005498026 11 2018-11-15 06:32:31.950 main[61953] *(p + [0]) : 1167938093 12 2018-11-15 06:32:31.950 main[61953] *(p + [1]) : 1512817253 13 2018-11-15 06:32:31.950 main[61953] *(p + [2]) : 1354576836 14 2018-11-15 06:32:31.950 main[61953] *(p + [3]) : 1468996356 15 2018-11-15 06:32:31.950 main[61953] *(p + [4]) : 965738374 16 2018-11-15 06:32:31.950 main[61953] *(p + [5]) : 92597726 17 2018-11-15 06:32:31.950 main[61953] *(p + [6]) : 206939477 18 2018-11-15 06:32:31.950 main[61953] *(p + [7]) : 1164748971 19 2018-11-15 06:32:31.950 main[61953] *(p + [8]) : 1551275529 20 2018-11-15 06:32:31.950 main[61953] *(p + [9]) : 2005498026