目录
一、相关定义
1.内存和地址
1.1内存
内存空间的管理是将内存划分为一个个的内存单元,每个内存单元的大小取一个字节;一个字节可以存放八个比特位,每个比特位可以存储一个2进制的1或0;每个内存单元有一个编号,通过这个编号可以迅速找到一个内存空间。
在计算机中将内存单元的编号称为地址,C语言中给地址起名为指针
也就是说,内存单元的编号==地址==指针
1.2地址与编址
CPU和内存之间的数据交互是通过地址总线来实现的。32位机器有32根地址总线,每根线有两种状态,表示0和1,即可表示两种含义,32根地址总线就能表示2^32种含义,每一种含义都代表一个地址。
2.指针变量和地址
2.1取地址操作符(&
#include <stdio.h>
int main()
{
int a = 10;
return 0;
}
上述代码创建了一个整型变量a,向内存中申请了一块空间,想要得到a的地址,则需要使用取地址操作符(&)
2.2指针变量和解引用操作符(*)
2.2.1指针变量
通过取地址操作符(&)拿到的地址需要存储时,就要使用到指针变量
例如
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
这段代码的作用就是,取出a的地址并存储到指针变量pa中
指针变量也是一种变量,只不过存放的是地址
2.2.2解引用操作符
指针变量中存放的地址需要使用时,则需要使用解引用操作符(*)
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0; //这句就是通过pa中存放的地址找到指向的空间,将a中的内容改成了0
return 0;
}
2.3指针变量的大小
指针变量的大小取决于地址的大小,32位平台下地址是32个bit位(4个字节);64位平台下地址是64个bit位(8个字节)
注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下,大小都是相同的
3.指针变量类型的意义
3.1指针的解引用
指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)
例如:char*的指针解引用只能访问一个字节,而int*的指针的解引用能访问四个字节
3.2指针+-整数
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
运行结果为
&n = 00AFF974
pc = 00AFF974
pc+1 = 00AFF975
pi = 00AFF974
pi+1 = 00AFF978
char*类型的指针变量+1跳过一个字节,int*类型的指针变量+1跳过了4个字节
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)
3.3void*指针
void*指针可以理解为无具体类型的指针(泛型指针),这种指针可以用来接受任意类型地址,但不可以直接进行指针的+-整数和解引用的运算
4.const修饰指针
4.1const修饰指针变量
#include <stdio.h>
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//测试⽆const修饰的情况
test1();
//测试const放在*的左边情况
test2();
//测试const放在*的右边情况
test3();
//测试*的左右两边都有const
test4();
return 0;
}
结论:const修饰指针变量的时候
- const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可以改变。
-
const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
5.指针运算
- 指针+-整数
- 指针-指针
- 指针的关系运算
6.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、正确的、没有明确限制的)
6.1野指针成因
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
6.2如何规避野指针
6.2.1指针初始化
明确知道指针指向哪里时直接赋值地址,不知道指针应该指向哪里,可以给指针赋值NULL(NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错)
6.2.2小心指针越界
一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问
6.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性
6.2.4避免返回局部变量的地址
7.assert断言
assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏称为“断言”。如果已经确定程序没有问题,不需要再做断言,就在#include<stdio.h>语句的前面,定义一个宏NDBUG
8.指针的使用和传址调用
使用传值调用时实参的数值不会改变,这时就需要采用传址调用
#include <stdio.h>
void Swap(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
传址调用可以让函数和函数主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以函数中只是需要主调函数中的变量值来实现计算,就可以采用传址调用;如果函数内部要修改主调函数中的变量的值,就需要传址调用。
二、指针与数组
1.数组名的理解
数组名就是数组首元素(第一个元素)的地址,但是有两个意外
- sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
2.使用指针访问数组
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
}
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
3.一维数组传参的本质
数组传参本质上传递的是数组首元素的地址
结论:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
4.二级指针
存放指针变量的地址的指针变量就是二级指针
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
ppa即为二级指针
5.指针数组
存放指针的数组
6.指针数组模拟二维数组
#include <stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
int* parr[3] = {arr1, arr2, arr3};
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
以上代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的
7.数组指针变量
7.1数组指针变量的定义
int (*p)[10];
存放的是数组的地址,能够指向数组的指针变量
7.2数组指针变量的初始化
int arr[10] = {0};
int (*p)[10] = &arr;
8.二维数组传参的本质
二维数组传参的本质是传递了地址,传递的是第一行这个一维数组的地址,形参也可以写成指针形式的
三、指针与函数
1.函数指针变量
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
通过函数指针调用指针指向的函数
2.函数指针数组
int (*)()类型的函数指针
int (*a[3])();
3.转移表
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while(input);
return 0;
}
转移表实现简单的计算器
4.qsort函数的模拟实现
#include <stdio.h>
#include <stdlib.h>
int compare(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 3,6,8,0,2,5,8,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), compare);
print(arr, sz);
return 0;
}