目录
指针可以说是c语言中非常核心的一个特征了,要了解它,我们可以先从内存和编址下手。
1.内存和编址
我们都知道手机,电脑等许许多多的电子产品都有内存,内存中存放着我们保存的信息,要知道内存是一块很大的空间。
所以为了很好的管理起这些空间,我们的电脑都是通过许多硬件来一起完成的。不过今天我们只关注:地址总线
一般来说多少位的机器就有多少根地址线,像32位的机器就有32根地址线,一根线可以通过改变有无脉冲来表示1或0,1根地址线就可以表示俩种含义,那么32根地址线就有2^32种含义,即每一种含义代表一个地址,内存就通过地址线来给cup下达命令,从而精确的找到内存中的任意空间。
看上面一段代码,开始调试,添加监视内存我们可以很清楚的看见一行行数字,这些就是内存中的地址了。
2.&(取地址)与*(解引用)
2.1—&
在上面的代码中我们可以很明显的看见我们创建的变量a的地址是:0X0000004122AFFC04。
想把a的地址给取出来,就要用到单目操作符:&(取地址)。
而它取出的地址都是要存放在指针变量中的。
2.2—*
指针变量:指针变量也是变量,只是指针变量是专门用来存放地址的。
当我们的地址存放到指针变量中我们想要找到指针变量(地址所指向)的数据时就要用到
单目操作符: *(解引用操作符)。通过解引用对指针变量进行操作就可以得到地址所指向的数据
2.3指针变量的类型与大小
类型 | 32位机器下 | 64位机器下 |
char* | 4个字节 | 8个字节 |
int*int* | 4个字节 | 8个字节 |
从上面列举的2种指针变量中我们得知,在同一平台下指针变量的大小都一样。
3.指针类型的意义
既然每种类型的指针大小都一样那为什么还要有这么多指针类型呢?主要有下面俩种原因:
3.1指针解引用
当我们创建一个指针变量int*p 和char *p它们指向的地址一样。
对char*p 与int *p进行解引用:*(p)解引用的不同之处在于char* p解引用只能访问1个字节的空间
而int* p却能访问4个字节的空间。
3.2对指针变量++或--整数
对指针变量进行整数的加减,实际是控制指针向前或向后移动的距离(字节):
指针指向的数据类型所占字节的大小
例如:
char* p指针进行+1,char* p指向的数据类型大小应为1个字节,所以char* p+1,跳过了一个字节
int* p指针进行+1,int* p指向的数据类型大小为4个字节,所以int* p+1,跳过了四个字节
注:拿int *p指向一个char数据也可以,但是int* p的本质是一个指向int数据类型的指针 ,所以对它进行加减,应该拿int的字节大小来算指针的移动距离。指针的移动距离还是得看指针的类型是什么
3.3特殊的void*型指针
void*型指针在相同的平台下的大小也一样,但是它特殊的点在于并不可以对它进行整数的加减
与解引用因为void*是无具体类型的指针,如果你对它进行这俩个操作,系统不知道操作几个字节
因此不可以直接对void*的指针进行上诉操作。
4.const修饰指针变量
const修饰指针左侧:const int* p 或int const * p | 可修改指针指向的对象,不可修改指针指向的内容 |
const修饰指针右侧:int *const p | 可修改指针指向的内容,不可修改指针指向的对象 |
const修饰指针左右俩测:const int* const p | 指针指向的内容,指针指向的对象均不能被修改 |
5.数组名与 &数组名
首先我们要认识:数组首元素地址与数组地址。
6.指针的运算
6.1指针加减整数
前面已简单介绍,这边在进行代码的加入,进行理解。
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* a = &arr[0];
a++;
printf("%d", *a);
return 0;
对指针变量a进行赋值,地址为数组arr首元素的地址,一开始,指针指向对象的内容是1。
让指针+1后,指针往后走4个字节,指向对象变成了数组的第二个元素。
在让a+1指向的就是第三个元素,依次类推,arr数组里的元素都可以遍历。
6.2指针减指针
先说结论:指针减指针的绝对值是俩个指针之间的元素个数。
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p1 = &arr[0];
int* p2 = &arr[9];
printf("%d", p2 - p1);
return 0;
}
以上代码和的输出结果是9,与我们研究的p1—p2之间的元素为9一样。
7.野指针的形成和预防
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
野指针形成的原因:
1.指针未初始化
int main() {
int* p;
printf("%d", *p);
return 0;
}
由于所以的指针变量刚创建出来时都不会自动赋值,所以它的值是随机的,如果使用了这样的指针系统就会报错。
2. 指针指向的空间释放
指针所指的内存被释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。例如:
int test() {
return 0;
}
int main() {
int(*p)() = test;
printf("%d", p);
return 0;
}
当指针指向释放空间时它就是一个野指针。
3. 指针操作超越变量作用域
int main()
{
int arr[5] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 6; i++)
{
*(p++) = i; //当指针指向的范围超出数组arr的范围时,p就是野指针
}
return 0;
}
野指针的预防
养成以下良好习惯,可以很有效的规避野指针:
1.创建指针变量时一定要初始化,指向有效变量的地址,或者指向NULL。
2.当指针p指向的内存空间释放时,设置指针p的值为NULL。
3.使用指针时判断是否合法,通常使用if语句判断是否为NULL
8.数组指针变量
数组指针变量顾名思义就是指向数组的指针变量,通过前面的介绍我们已经知道有:int* char*
型的指针变量,数值指针变量长这样:数组元素类型(*数组名)[数组元素个数]。
举例:int (*p) [10] 这个数组指针变量指向一个元素为int型元素个数为10个的数组,数组名为p。
为什么呢?
* 先与p结合,说明它是一个指针,指向类型为int [ 10 ]的数组
注意!这里的括号不能少,因为 [ ] 的优先级比 * 高,没有括号, [ ]就会先和指针名结合,那么它就不是一个指针了,得先让 * 和指针名结合,才能说明它是一个指针。
8.1数组指针变量的初始化
int main()
{
int arr[5] = { 0 };
int(*p)[5] = &arr;
return 0;
}
将数组的地址使用&符号取出,赋值给数组指针变量即可。
9.数组指针变量加减整数
前面已经说过,对指针变量加减整数就是让指针越过一定的距离(字节)。
而越过的距离(字节)取决于指针的类型。
例如 int(*p1)[5]
*先与p1结合,说以p1是一个指针,指向一个类型为int [ 5 ] 的数组
所以当我们对int(*p1)[5]指针变量进行加减的时候,越过的是类型为int [ 5 ] 的数组的大小
10.数组传参本质
10.1 一维数组传参
在此之前,你是否有个疑问,为什么数组元素的计算都要在数组外部?
void test(int arr[5],int sz1) {
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz1); //打印5
printf("%d\n", sz2); //打印1
}
int main() {
int arr[5] = { 1,2,3,4,5 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
test(arr, sz1);
return 0;
}
前面说过只有在sizeof()里的数组名和&数组名才能拿到一整个数组地址,其他的数组名都是首元素地址,传参过去的arr一样也是首元素地址。
使用指针为形参
void test(int* arr)//参数写成指针形式,指针变量为数组首元素的类型
{
printf("%d\n", *arr);
}
int main()
{
int arr[5] = { 1,2,3,4,5};
test(arr);
return 0;
}
总结:一维数组传参,形参的部分既可以写成数组形式,也可以写成指针形式。
10.2 二维数组传参
void test(int arr[3][4]) {
}
int main() {
int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6,} };
test(arr);
return 0;
}
创建一个arr数组,研究arr在内存中的存放情况
前面我们也说过,传参传过去的数组名是首元素的地址,其实这句话放在二维数组上也没有错。
arr(二维数组)传过去的就是,arr的首元素。
使用指针为新参
void test(int (*arr)[4]) //参数写成指针形式,指针变量为数组首元素的类型
{
}
int main() {
int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6,} };
test(arr);
return 0;
}
总结:二维数组传参,形参的部分既可以写成数组形式,也可以写成指针形式。