十、指针
使用&
取地址
对于scanf 将输入的值传给一个变量 那么要加上&
符号
scanf("%d",&i);
C语言的变量是放在内存中的 每个变量都有个地址 地址就是变量在内存中所存放的地方的位置
而&
符号 就能够获取到指定变量的地址 它是C语言中的取地址符
获取变量的地址 它的操作数必须是变量 而不能对没有地址的取地址
使用%p
来输出地址:
int i=0;
printf("%p\n",&i);
地址的大小取决于编译器和系统是32位还是64位架构
相邻的变量的地址
同时定义的两个变量 它们在内存中的地址位置也是紧挨着的 是相邻的
int i1;
int i2;
printf("%p\n",&i1); // 0061FECC
printf("%p\n",&i2); // 0061FEC8
这是因为 在堆栈中 内存是自顶向下分配的 因此先定义的变量会在上面(即地址值更高)
数组的地址
数组的引用和数组的取地址是一样的 都是数组的地址
数组的地址使用的是数组中第一项(也就是下标为0)的地址
且数组中的每一项之间都是相邻的 紧贴着的
int i[10];
printf("%p\n",&i); // 0061FEA8
printf("%p\n",i); // 0061FEA8
printf("%p\n",&i[0]); // 0061FEA8
printf("%p\n",&i[1]); // 0061FEAC
printf("%p\n",&i[2]); // 0061FEB0
指针变量
将取得的变量的地址传递给一个函数 那么可以通过该地址在那个函数内访问这个变量
【指针】 是一种变量的类型
指针类型的变量就是专门用于保存地址的变量
下面是一个简单的例子:
将int类型的变量 i 的地址交给了指针p
int*中的*
代表这是一个指针 int
表示该指针指向的是int
int i;
int* p=&i;
p里保存的是变量i的地址 那么 可以说是p指向i
指针变量
星号*可以靠近类型 也可以靠近变量名
因此 并不是将星号加给了int这个类型 而是加给了距离星号最近的那个变量名
int* a,b; // 此时 a是指针类型 而b只是普通的int类型
int *a,b; // 此时 a是指针类型 而b只是普通的int类型
在C中并没有int*这个类型
指针变量的特点
在普通的变量中 存放的是实际的值
而在指针变量中 不会存放实际的值 存放的是具有实际的值的变量的地址
指针作为参数
在指针作为参数的时候 用void f(int *p);
来定义
在调用的时候 要这么传入:
int i=123;
f(&i); // 传入的是int类型的变量的【引用】而不是原本的变量名或值
访问指针地址上的变量
若要访问 也很简单 只需要使用星号即可
当然 星号若作为双目运算符 那么是乘的意思
但作为单目运算符 用于访问指针的值所表示的地址上的变量
#include <stdio.h>
void f(int *p);
int main()
{
int i=123;
printf("%p\n",&i); // 0061FECC
f(&i);
return 0;
}
void f(int *p)
{
printf("%p\n",p); // 0061FECC
printf("%d",*p); // 123
}
*n
可以作为右值也可以作为左值
比如:
int i=*p;
*p=i+2;
可以直接通过指针来很方便地直接改变指针所指向的变量的值:
#include <stdio.h>
void f1(int *p);
void f2(int *p);
int main()
{
int i=123;
printf("%p\n",&i); // 0061FECC
f1(&i);
f2(&i);
return 0;
}
void f1(int *p)
{
printf("%p\n",p); // 0061FECC
printf("%d\n",*p); // 123
// 修改指针指向的位置的值
*p=321;
}
void f2(int *p)
{
printf("%p\n",p); // 0061FECC
printf("%d",*p); // 321
}
左值 之所以被称为左值 就是因为在赋值号=
左边的并不是变量 而是一个实实在在的值 是表达式计算后的结果 是特殊的值
i[0]=123;
*p=123;
指针运算符的互相反作用
在指针中 用&
获取地址 用*
获取指定地址所代表的变量
它们是互相反作用的 即:
- 若对一个获取的地址取所代表的变量 那就是原来的变量
- 同样 若对一个指针所指向的变量取地址 那就是原来的地址
指针的应用场景
在使用指针之后 即可用其交换变量
由于函数的作用域范围问题 直接将值传入函数内 那么值在函数外 是不会被改变的
但若传入的是指针 那么即可改变指针所指向的地址上的值了
#include <stdio.h>
void swap(int *pa,int *pb);
int main()
{
int a=1;
int b=2;
swap(&a,&b);
printf("%d\n",a); // 2
printf("%d\n",b); // 1
return 0;
}
void swap(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
}
因为函数只能有一个返回值 无法返回多个值 (而且也不能像Java一样返回一个对象… )
那么 当函数返回的是多个值 那么值只能通过指针返回
或者 遇到另一种情况 就是要分开返回结果
C中不像Java可以异常处理
在C中 只能函数返回(return)运算的状态 而函数的结果通过指针来返回
指针的注意点
需要注意的是 指针必须先指向一个变量的地址 然后才能被赋值
否则 指针里面什么都没有 此时可能默认指向的是内存中的一块未知区域 然后此时就被赋值了 那么可能会导致程序崩溃
指针和数组
在函数中通过参数接收到的外界的数组 其实只是数组的指针
在函数中接收到的数组的地址和外部的数组的地址是完全一模一样的
但 可以用数组的运算符[]
来对该数组进行运算或做其它操作
因此:
int f(int *arr);
其实等于
int f(int arr[]);
在C中 数组变量是特殊的指针 数组变量本身表达的就是地址
因此 对于数组 无需用&
来取地址了(这是多此一举)
int arr[10];
int *p=arr; // 这样就行了
但数组的单元表达的是单个的变量 因此需要用&
来取地址 因为里面存放的是确切的值
数组变量名其实就是数组的地址 a=&a[0]
int a[]={ 1,2,3};
printf("%x\n",a); // 61feac
printf("%x\n",&a); // 61feac
printf("%x\n",&a[0]); // 61feac
[]
运算符除了可以对数组使用 同样也可以对指针使用
对于指针变量 可以将一个普通变量当作是数组
int i=123;
int* p=i;
printf("%d\n",p); // 123
printf("%d",&p[0]); // 123
同样的 *
运算符除了可以对指针使用 同样也可以对数组使用
int i[]={ 12,23,34};
printf("%d",*i); // 12
在C中 数组变量实际上是const(常量)的指针 因此不能被赋值
int a[]={ 1,2,3};
int b[]=a; // ×
int *p=a; // √
int b[] --> int * const b;
在C99中 指针本身和指针所指向的那个值 都可以是const
情况一
、指针本身为const
一旦得到某个变量的地址 那么不能再指向其它变量 相当于是两者进行了绑定了
int i=123;
const int *p=&i;
*p=321; // √
p++; // ×
情况二
、指向的值为const
无法通过指针去修改该变量(但并不意味着不能修改该变量)
int i=123;
int j=111;
const int *p=&i;
*p=321; // ×
i=321; // √
p=&j; // √
const在*的前面 则代表指针所指向的值不能被修改
const在*的后面 则代表该指针不能被修改
int i;
const int* p1=&1;
int const* p2=&i;
int *const p3=&i;
转换
可以将非const的值转换为const的值
void f(const int* i);
当要传递的参数的类型比地址还要大的时候 可用该方法 传递一个const的指针变量
如此 既能用较少的字节数来传递值给参数 同时又能避免函数外面对变量进行修改
const数组
数组变量已经const的指针了 再加const 代表数组中的每个单元都是const的int类型的值 不能再改变
const int arr[]={ 1,2,3,4,5};
因此 为保护数组不被函数修改 可设置该函数的参数为const
int f(const int arr[],int length);
指针的运算
当给指针+1的时候 实际上指针所增加的不一定是1
指针所增加的是sizeof(类型)
的值
printf("%d\n",sizeof(char)); // 1
printf("%d\n",sizeof(int)); // 4
那么 char类型的指针+1 地址值增加的就是1
int类型的指针+1 地址值增加的就是4
由于数组中存放的值都是紧挨着的 且指针默认指向的是数组中的第一位 那么 指针 +1实际上就是让指针指向数组中的下一位
int arr[]={ 12,23,34,45,56};
int *p=arr;
printf("%p\n",p); // 0061FEB8 此时指向的是数组arr中的12这个数
printf("%p",p+1); // 0061FEBC 此时指向的是数组arr中的23这个数
同理 指针 -1实际上就是让指针指向数组中的上一位
printf("%p",p-1); // 0061FEB4
可以将指针看作是数组 也可以将数组看作是指针
那么 实际上 因为指针p默认是指向数组arr[0]
因此 当指针+1后 指针(p+1)指向的是数组arr[1]
当指针+2后 指针*(p+1)指向的是数组arr[2] 以此类推
int arr[]={ 12,23,34,45,56};
int *p=arr;
printf("%p\n",p); // 0061FEB8
printf("%d\n",*p); // 12
printf("%p\n",p+1); // 0061FEBC
printf("%d\n",*(p+1)); // 23
printf("%p\n",p-1); // 0061FEB4
printf("%d\n",*(p-1)); // 4201696 下标越界后 取到的就是一串地址值
*(p+n) <==> arr[n]
注意
这一切的运算都是基于指针指向的是连续空间 例如数组
若指针指向的压根不是一片连续的空间 那又何来上一位下一位之说?
那就根本没有意义了
指针可以做哪些计算
- 为指针加/减一个整数:
+ += - -=
- 递增/递减:
++ --
*p++
即为 取出指针p所指向的位置的数据 在结束后将指针p指向的位置移到下一个 *p- -同理 - 两个指针相减:
相减后的差 不是地址值的差 而是地址值的差/sizeof(类型)
即为它们中间相距几个该类型的距离
指针还可以进行比较
<
<=
==
>
>=
!=
这些 都可以对指针使用
当然 比较的并不是指针的值的大小 而是比较指针在内存中的地址的大小
0地址
在计算机的内存中 有个0地址 该地址无法读写
因此 0地址通常可以用于表达状态 例如:
- 返回的指针无效
- 指针未被正常初始化(指针先初始化为0)
但是 若用0来表示0地址 则有可能会崩溃
在C语言中 使用 NULL
(全部大写)来表示0地址
指针的类型
用void*
来表示不知道指向什么类型的指针 (只知道有一个指针指向某个内存空间 仅此而已)
指向不同类型的指针无法互相赋值 因此需要避免用错了指针
当然 也可以进行强制类型转换
int i=123;
int *p=&i;
void *q=(void*)p;
上述代码的含义是将int类型的指针p强制类型转换为void类型的指针q 这并没有改变指针p指向的变量i的类型
若通过指针q看指针p 那么它就是void类型的
若通过指针p看 那么他依旧还是int类型的
指针的用途
- 在需要传入较大的数据时 指针可以用作参数
- 在函数传入数组后 可以用指针对数组进行操作
- 当函数返回不止一个结果 可以用指针作为参数带出结果
- 当需要用函数修改不止一个变量 可以传入指针来修改变量的值
- 还可以动态申请内存
动态内存分配 / malloc&free
当遇到一种情况:预先不知道数组的大小 当输入的时候才知道
在这种情况下 C99可以用变量来定义数组的大小
但在C99之前 并不支持这么做 在这种情况下 就必须使用动态内存分配了
使用 malloc
函数来指定要分配多少内存
(使用前需导入stdlib头文件#include <stdlib.h>
)
malloc所要的参数是占据多少空间(以字节为单位)
malloc返回的是void*
的类型 因此还需要强制类型转换
比如这样:
int n;
scanf("%d",&n);
int *a=(int *)malloc(n*sizeof(int)) // 分配一块用于存放int类型的内存空间
最后 在使用完后还需使用free函数将内存归还
free是和malloc相配套的函数 用于将申请来的空间还给计算机系统
在归还的时候 只能还申请来的空间的首地址 而不是地址多次改变后的值
free(a); // 归还内存空间
若不free的话 随着长时间运行 计算机的内存会逐渐下降 导致产生内存垃圾
==================== 例子 ====================
作为例子
可以用动态类型分配来实现自动控制数组大小:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int number;
int* arr=0;
int i;
scanf("%d",&number);
arr=(int *)malloc(number*sizeof(int));
for (i=0;i<number;i++)
{
scanf("%d",&arr[i]);
}
for (i=number-1;i>=0;i--)
{
printf("%d\n",arr[i]);
}
// 动态内存分配 在用完之后 还需将内存归还
free(arr);
return 0;
}
注意点:
在main函数的参数中 最好将参数定义成int argc,char const *argv[]
main(int argc,char const *argv[])
{
...
}
argc是启动C程序时的参数个数 argv是启动C程序时的参数数组的指针 (这里的参数就是在运行C程序时在命令行输入的一串字符 用空格进行分隔)
(就像Java的main函数的String [] args)