C语言基础(二)——指针
iwehdio的博客园:https://www.cnblogs.com/iwehdio/
导图
1、指针的概念
- 取地址运算符 & :获得变量的地址,它的操作数必须是变量。
- 输出变量 i 的地址:
printf("%p", &i)
。(int)&i 与取到的地址是否一致取决于编译器位数。 - 相邻的变量的地址是紧挨着的,但是由于内存模型是堆栈,地址分配是自顶而下的,后面的元素地址反而靠前。
- & 的结果的地址 sizeof 在32位下是 4,在64位下是 8。
- 数组的地址:
- 对于数组
int a[10]
,a 与 &a 都表示数组a中第一个元素的地址,即 &a[0]。 - 每两个相邻的数组单元的地址是顺序紧邻的。
- 对于数组
- 指针:保存地址的变量。
- 指针的定义:
int *p = &i
,无论 * 号靠近 int 还是靠近 p,表示的都是 p 是指向 int 类型的指针, 变量 p 存储的地址指向变量 i 。 - 指针变量的值是内存的地址。
- 指针作为函数的参数时:
- 函数原型中,指定了参数类型是 指向 int 的指针:
void f(int *p)
。 - 调用时,传入的要是某个变量的地址:
f(&i)
。
- 函数原型中,指定了参数类型是 指向 int 的指针:
- 建议在指针变量定义时,赋值为所指向的变量或者指向0。
- 指针的定义:
- 取内容运算符 * :访问指针的值所表示的地址上的变量的值。
- 如果指针 p 是指向 int 类型的,则 *p 可以被看作一个整体,这个整体是 int 类型的变量。
- 可以通过将 *p 作为左值,更改 p 存储的地址指向的变量的值:
*p = 新值
。 - 左值:出现的赋值号左边的不是变量,而是值,是表达式运算的结果。
2、指针的使用
-
指针的应用场景:
- 需要传入较大的数据时用作参数;
- 传入数组后对数组做操作;
- 函数返回不止一个结果;
- 需要用函数来修改不止一个变量;
- 动态申请的内存。
-
指针的使用:
-
经典的交换两个变量的值:
void swap(int *a, int *b){ int t = *a; *a = *b; *b = t; }
-
函数返回多个值,某些值就只能通过指针返回。
- 传入的参数实际上是需要保存带回的结果的变量。
- 或者,函数返回运算的状态,结果通过指针返回。
- C++/Java 会使用异常来处理。
-
不能对没有设置指向的指针进行操作。
-
如果在函数声明或指针定义时,* 只是表示所接收的变量或定义的变量是指针类型。这时 * 表示其后定义的变量是指针类型。
-
如果在函数声明或指针定义以外,* 表示取地址运算符,构成一个整体表示指向的变量内容。
-
-
指针与数组:
-
当数组作为参数传入函数时,实际上传入的是指针。
-
以下表示在函数的参数列表中是等价的:
int *a
,int *
,int a[]
,int []
。 -
数组变量是特殊的指针,数组变量本身就表示地址。
-
但是数组的元素表达的是变量,需要用 & 取地址。
-
* 运算符可以对指针做,也可以对数组做。
-
数组变量是 const 的指针,所以不能被赋值。
-
-
指针与 const :
-
const 表示其修饰的变量是不可改变的。
-
如果指针是 const :
-
表示一旦得到了某个变量的地址,不能再指向其他变量。
-
如:
int * const q = &i; //q是const *q = 26; //可以做 q++; //不可以做
-
-
如果指针指向的变量是 const :
-
表示不能通过指针去修改这个变量。
-
如:
const int * p = &i; //*p是const *p = 26; //不可以做 i = 26; //可以做 p = &j; //可以做
-
-
判断哪个被 const 修饰的标志是 const 在 * 的前面还是后面,在星号前边则所指被修饰,在星号后边则指针被修饰。
-
const 数组:
- 定义格式:
const int a[]={1,2,3}
。 - 数组变量已经是 const 的指针了,这里的 const 表明数组的每个单元都是 const int。
- 所以必须通过初始化进行赋值。
- 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值。
- 为了保护数组不被函数破坏,可以设置参数为 const 。
int f(const int a[])
。
- 定义格式:
-
-
转换:总是可以把一个非 const 的值转换成 const 的。
-
如:
void f(const int* x); //如果传入的不是 const int a = 15; f(&a); //在函数f内部转换成const,也就是说在函数f内部不会更改指针所指向的值 //如果传入的是const const int b = a; f(&b);
-
当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。比如传入数据结构时。
-
-
指针运算:
- 对指针加一时,指针存储的地址实际上是加了 sizeof(指针指向的数据类型) 。表示的意义是将指针指向下一个元素。
- 如:对于 数组 a,
char *p = a
,*p
表示 a[0] 时,则*(p+n)
表示 a[n]。 - 如果指针不是指向一片连续分配的空间如数组,则这种运算没有意义。
- 指针相减,得到的是两个地址的差除以 sizeof(指针指向的数据类型) 。表示的意义时两个指针之间的元素个数。
- 经典用法:
*p++
- 取出 p 所指的那个数据来,完事之后顺便把 p 移到下一个位置去。
- * 的优先级虽然高,但是没有 ++ 高。
- 常用于数组类的连续空间操作。
- 在某些CPU上,这可以直接被翻译成一条汇编指令。
- 指针的比较:
- 大于、等于、相等,比较的是内存中的地址。
- 数组中单元的地址是线性递增的。
-
0 地址:
- 内存中有0地址,但是0地址通常是个不能随便碰的地址。
- 所以指针不应该具有0值,因此可以用0地址来表示特殊的事情:
- 返回的指针是无效的。
- 指针没有被真正初始化(先初始化为0)。
- NULL是一个预定义的符号,表示0地址。
-
指针的类型:
- 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址。
- 但是指向不同类型的指针是不能直接互相赋值的,这是为了避免用错指针。
-
指针的类型转换:
- void* 表示不知道指向什么东西的指针。
- 计算时与 char* 相同(但不相通)。
- 指针可以转换类型:
- 指针的强制类型转换:
int*p=&i;void*q=(void*)p
。 - 这并没有改变 p 所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量。
- 指针的强制类型转换:
- void* 表示不知道指向什么东西的指针。
-
动态内存分配:
- 导入头文件:
#include <stdlib.h>
。 - 申请空间:
int* a = (int*)malloc(n * sizeof(int))
。- 用指向 int 的指针 a 指向分配到的数组。
- n * sizeof(int) 表示存储 n 个 int 类型单元的空间。
- (int*) 表示强制类型转换 从 void* 到 int* 。
- 将申请到的空间返还给系统:
free(a)
。- 只能还申请来的空间的首地址。
- 向 malloc 申请的空间的大小是以字节为单位的。
- 返回的结果是 void*,需要类型转换为自己需要的类型。
- 内存申请失败时会返回 null 。
- 导入头文件: