3. 指针与地址
(1) & 与 * 操作
取地址运算符 & : p = &c;
-- 表达式解析 : 将 c 的地址赋值给 变量 p, p 是指向 c 变量的指针;
-- & 可以使用的情况 : 取地址操作 只能用于内存中的对象, 如变量 或 数组, 栈内存 堆内存 都可以;
-- & 不适用的情况 : 不能用于 表达式, 常量, register类型变量;
间接引用运算符 : * ;
-- 声明指针 : int *p ; 该表达式的含义是 *p 的结果是 int 类型, 声明变量 a, int a, 声明指针 *p , int *p;
-- 获取指针指向的值 : int a = *p ;
(2) 指针定义解析
声明指针 和 函数 : int *p, max(int a, int b), 声明指针变量 语法 与声明 变量语法类似, 同理声明函数也一样;
-- 原理 : *p 和 max()返回值 类型都是 int 类型;
指针指向 : 每个指针都必须指向某种特定类型;
-- 例外 : void *p 可以指向任何类型, 但是 p 不能进行取值运算, *p 是错误的, 因为不知道 p 指向的数据类型;
(3) 指针运算及示例
指针相关运算 : int x = 0; int *p = &x; 那么*p 就可以代表x;
-- 算数运算 : x = x + 1; 等价于 *p = *p + 1 ; int y = x + 1; 等价于 int y = *p + 1;
-- 自增运算 : 前提 : ++, * 运算顺序是自右向左; ++*p 和 (*p)++, p 指向的值自增1, 注意要加上括号, 否则会将地址自增;
-- 指针赋值 : int *p, *q; int a = 0; p = &a; q = p; 最终结果 p 和 q 都指向了 变量 a;
示例程序 :
/************************************************************************* > File Name: pointer_address.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Mon 10 Mar 2014 09:52:01 PM CST ************************************************************************/ #include<stdio.h> int main(int argc, char ** argv) { int *p, *q; int a = 10, b; //p指针指向a变量 p = &a; //*p 可以代替 a 进行运算 ++*p; b = *p + 5; //指针之间可以直接相互赋值 q = p; //打印 p 和 q 指针指向的值 printf("*p = %d \n", *p); printf("*q = %d \n", *q); return 0; }
执行结果 :
[root@ip28 pointer]# gcc pointer_address.c [root@ip28 pointer]# ./a.out *p = 11 *q = 11
4. 函数参数的传值调用和传址调用
(1) 传值调用 和 传址调用
传值调用 : 以传值的方式将参数传递给函数, 不能直接修改主函数中变量的值, 仅仅是将副本传递给了函数;
传址调用 : 将 变量的指针 传递给函数, 当函数对指针进行操作的时候, 主函数中的值也进行了对应变化;
交换函数示例1 :
/************************************************************************* > File Name: swap.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Mon 10 Mar 2014 11:07:18 PM CST ************************************************************************/ #include<stdio.h> void swap_1(int a, int b) { int temp; temp = a; a = b; b = temp; printf("swap_1 传值 函数 a = %d, b = %d \n", a, b); } void swap_2(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; printf("swap_2 传址 函数 a = %d, b = %d\n", *a, *b); } int main(int argc, char **argv) { int a = 10, b = 5; printf("初始值 : a = %d, b = %d \n\n", a, b); swap_1(a, b); printf("执行 swap_1 函数, a = %d, b = %d \n\n", a, b); swap_2(&a, &b); printf("执行 swap_2 函数, a = %d, b = %d \n", a, b); return 0; }
执行结果 :
[root@ip28 pointer]# gcc swap.c [root@ip28 pointer]# ./a.out 初始值 : a = 10, b = 5 swap_1 传值 函数 a = 5, b = 10 执行 swap_1 函数, a = 10, b = 5 swap_2 传址 函数 a = 5, b = 10 执行 swap_2 函数, a = 5, b = 10
示例解析 :
-- 传值调用 : swap_1 是传值调用, 传入的是 main 函数中的 a b 两个变量的副本, 因此函数执行完毕后, 主函数中的值是不变的;
-- 传址调用 : swap_2 是传址调用, 传入的是 a , b 两个变量的地址 &a, &b, 当在swap_2 中进行修改的时候, 主函数中的 a,b变量也会发生改变;
(2) 高级示例
需求分析 : 调用getint()函数, 将输入的数字字符 转为一个整形数据;
getch 和 ungetch 函数 :
-- 使用场景 : 当进行输入的时候, 不能确定是否已经输入足够的字符, 需要读取下一个字符, 进行判断, 如果多读取了一个字符, 就需要将这个字符退回去;
-- 使用效果 : getch() 和 ungetch() 分别是预读下一个字符, 和 将预读的字符退回去, 这样对于其它代码而言, 没有任何影响;
注意的问题 : 出现问题, 暂时编译不通过, 找个C语言大神解决;
代码 :
/************************************************************************* > File Name: getint.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Mon 10 Mar 2014 11:40:19 PM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define SIZE 5 int getint(int *p) { //sign 是用来控制数字的正负 int c, sign; //跳过空白字符, 如果是空白字符, 就会进行下一次循环, 直到不是空白字符为止 while(isspace(c = getc(stdin))); //如果输入的字符不是数字, 就将预读的数据退回到标准输入流中 if(!isdigit(c) && c != EOF && c != '+' && c != '-') { ungetc(c, stdin); return 0; } /* * 如果预读的是减号, 那么sign 标识就是 -1, * 如果预读的是加号, 那么sign 标识就是 1; */ sign = (c == '-') ? -1 : 1; //如果 c 是 加号 或者 减号, 再预读一个字符& if(c == '+' || c == '-') c = getc(stdin); for(*p = 0; isdigit(c); c = getc(stdin)) *p = 10 * *p + (c - '0'); *p *= sign; if(c != EOF) ungetc(c, stdin); return c; } int main(int argc, char **argv) { int n, array[SIZE], i; for(n = 0; n < SIZE && getint(&array[n]) != EOF; n++); for(i = 0; i < SIZE; i++) { printf("array[%d] = %d \n", i, array[i]); } return 0; }
执行结果 :
octopus@octopus-Vostro-270s:~/code/c/pointer$ ./a.out 123 123 43 674 1 array[0] = 123 array[1] = 123 array[2] = 43 array[3] = 674 array[4] = 1
5. 指针 和 数组
指针数组比较 :
-- 可互相替代 : 数组下标执行的操作都可以使用指针替代;
-- 效率比较 : 使用指针操作效率比数组要高;
指针 与 数组初始化 :
-- 声明数组 : int a[10]; 定义一个长度为10 的int数组;
-- 声明指针 : int *p; 定义一个指针, 该指针指向整型;
-- 相互赋值 : p = &a[0], 将数组第一个元素的地址赋值给指针变量;
-- 使用指针获取数组对象 : *p 等价于 a[0], *(p + 1) 等价于 a[1], *(p + i)等价于 a[i];
-- 注意地址的运算 : p + i , 在地址运算上, 每次增加 sizeof(int) * i 个字节;
将数组赋值给指针的途径 :
-- 将数组第一个元素地址赋值给指针变量 : p = &a[0];
-- 将数组地址赋值给指针变量 : p = a;
指针 和 数组 访问方式互换 : 前提 int *p, a[10]; p = a;
-- 数组计算方式 : 计算a[i]的时候, 先将数组转化为 *(a + i)指针, 然后计算该指针值;
-- 取值等价 : a[i] 等价于 *(p + i);
-- 地址等价 : &a[i] 与 a + i 是等价的;
-- 指针下标访问 : p[i] 等价于 *(p + i);
-- 结论 : 通过数组和下标 实现的操作 都可以使用 指针和偏移量进行等价替换;
指针 和 数组 的不同点 :
-- 指针是变量 : int *p, a[10]; p = a 和 p++ 没有错误;
-- 数组名不是变量 : int *p, a[10]; a = p 和 a++ 会报错;
数组参数 :
-- 形参指针 : 将数组传作为参数传递给函数的时候, 传递的是数组的首地址, 传递地址, 形参是指针;
数组参数示例 :
-- 函数参数是数组 : 函数传入一个字符串数组参数, 返回这个字符串长度;
/************************************************************************* > File Name: array_param.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Sat 15 Mar 2014 12:46:57 AM CST ************************************************************************/ #include<stdio.h> //计算字符串长度 int strlen(char *s) { int n; for(n = 0; *s != '\0'; s++) n++; return n; } int main(int argc, char** argv) { printf("strlen(djdhaj) = %d \n", strlen("djdhaj")); printf("strlen(12) = %d \n", strlen("12")); printf("strlen(dfe) = %d \n", strlen("dfe")); }
-- 执行结果 : warning: conflicting types for built-in function ‘strlen’, 原因是 C语言中已经有了 strlen 函数了, 如果改一个函数名, 就不会有这个警告了;
[root@ip28 pointer]# gcc array_param.c array_param.c:12: warning: conflicting types for built-in function ‘strlen’ [root@ip28 pointer]# ./a.out strlen(djdhaj) = 6 strlen(12) = 2 strlen(dfe) = 3
数组和指针参数 : 将数组名传给参数, 函数根据情况判断是作为数组还是作为指针;
-- 实参 : 指针偏移量 和 数组下标 都可以作为 数组或指针函数形参, 如 数组情况fun(&array[2]) 或者 指针情况fun(p + 2);
-- 形参 : 函数的形参可以声明为 fun(int array[]), 或者 fun(int *array), 如果传入的是数组的第二个元素的地址, 可以使用array[-2]来获数组取第一个元素;
数组指针参数示例 :
/************************************************************************* > File Name: param_array_pointer.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Sat 15 Mar 2014 01:28:33 AM CST ************************************************************************/ #include<stdio.h> //使用指针做形参 取指针的前两位 和 当前位 void fun_p(int *p) { printf("*(p - 2) = %d \n", *(p - 2)); printf("*p = %d \n", *p); } //使用数组做形参 取数组的 第-2个元素 和 第0个元素 void fun_a(int p[]) { printf("p[-2] = %d \n", p[-2]); printf("p[0] = %d \n", p[0]); } int main(int argc, char **argv) { int array[] = {1,2,3,4,5}; //向指针参数函数中传入指针 printf("fun_p(array + 2) : \n"); fun_p(array + 2); //向数组参数函数中传入数组元素地址 printf("fun_a(&array[2]) : \n"); fun_a(&array[2]); //向指针参数函数中传入数组元素地址 printf("fun_p(&array[2]) : \n"); fun_p(&array[2]); //向数组参数函数中传入指针 printf("fun_a(array + 2) : \n"); fun_a(array + 2); return 0; }
执行效果 :
[root@ip28 pointer]# gcc param_array_pointer.c [root@ip28 pointer]# ./a.out fun_p(array + 2) : *(p - 2) = 1 *p = 3 fun_a(&array[2]) : p[-2] = 1 p[0] = 3 fun_p(&array[2]) : *(p - 2) = 1 *p = 3 fun_a(array + 2) : p[-2] = 1 p[0] = 3