指针=地址!!!
与普通变量不同,指针的大小是统一的。在32位操作系统下,指针的大小都是4字节,在64位操作系统下,大小为8字节。
一、指针的解引用
对指针变量前加*号的意思是对这个指针进行解引用,也就是访问它所指向的空间。通过指针的解引用,我们就可以向使用普通变量那样使用指针。
char* 类型的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。
二、指针加减整数
char * pc;//定义字符型指针
int * pi;//定义整形指针
char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
三、void * 指针
这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。
⼀般 void* 类型的指针是使⽤在函数参数的部分,用来接收不同类型的数据的地址。
四、const修饰指针
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量。
int *p = &a;
int const * p = &a;
const如果放在 * 的左边,修饰的是指针指向的内容,意思是指针指向的内容(a)不能通过指针来改变。
int *const p = &a;
const如果放在 * 的右边,修饰的是指针变量本身,保证了指针变量的内容(a的地址)不能修改,但是指针指向的内容(a),可以通过指针改变。
五、assert断言
assert()的使用需要包含头文件assert.h。
assert断言的意思是接受一个表达式作为参数,如果该表达式为真,(返回值非零),assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写入⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
它不仅能自动标识文件和出问题的行号,还有⼀种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include 语句的前面,定义⼀个宏NDEBUG 。
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
六、传值调用和传址调用
传值调用:将值传给函数,不能在函数内部修改主函数变量。
传址调用:将地址传给函数,可以通过函数参数接收地址,通过地址修改主函数变量。
七、数组名的理解
数组名是首元素的地址。
两个例外:1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节。2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)
&arr :表示整个数组的地址,&arr + 1表示跳过整个数组。
八、使用指针访问数组
int * p = arr;
使用arr[i]可以访问数组,使用p[i]也可以访问数组。
*(p + i) 等价于 p[i]。
九、一维数组传参的本质
传递的是数组首元素的地址。
十、二级指针
存放指针的变量。
十一、指针数组
存放指针的数组。
上面的模拟的是二维数组。parr[]数组的类型是int* 。数组里面的元素是每个一维数组首元素的地址。
十二、字符指针变量
const char* pstr = "hello bit.";
代码的是本质是把字符串 “hello bit.” ⾸字符的地址放到了pstr中。
十三、数组指针变量
int (*p)[10];
p先和*结合,说明p是⼀个指针变量,然后指着指向的是⼀个大小为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。
如果要存放整个数组的地址,就要存放在数组指针变量中。
int(*p)[10] = &arr;
十四、二维数组传参的本质
传递的是第一行数组的地址。
十五、函数指针变量
函数指针变量用来存放函数的地址,可以使用函数的地址调用函数。
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;
两种写法都可以。
函数指针变量的使用:
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));//两种写法都可以
return 0;
}
输出结果
5
8
十六、typedef关键字
typedef 是用来类型重命名的。
typedef unsigned int uint;
//将unsigned int 重命名为uint
指针类型命名:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别。
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t:
typedef void(*pfun_t)(int);
十七、函数指针数组
存放函数的地址。
函数指针数组的定义:
int (*parr1[3])();
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
//int (*指针名[元素数量]) (参数类型) = {元素1,元素2,......};
十八、回调函数
将一个函数的地址传给另一个函数,这个函数的作用是调用地址所指向的函数,实现相应的功能。
十九、qsort函数的用法
qsort函数是一种使用快速排序的函数。
它需要四个参数:
qsort(待排序数组的起始位置,元素个数,待排序数组的元素大小,函数指针)
qsort函数的使用者得自己实现一个比较函数,用来提供比较方法。
函数指针指向一个函数 compare(指针1,指针2),这个函数需要自己实现。
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);//如果是p2-p1,则是降序排序。
}// void类型的指针无法直接解引用,需要强制转换int*型。
//这个函数是由自己实现的一个函数,目的是向qsort函数提供一个比较方法。
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
// 首元素地址,元素个数, 元素类型 函数指针
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
二十、sizeof和strlen函数的对比
sizeof: 是操作符,计算变量所占内存内存空间大小的,单位是 字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间大小。
strlen:是C语言库函数,使用需要包含库函数<string.h>。功能是求字符串长度。统计的是从 strlen 函数的参数中这个地址开始向后, ‘\0’ 之前字符串中字符的个数。 strlen 函数会⼀直向后找‘ \0’ 字符,直到找到为止,所以可能存在越界查找。
二十一、数组名的意义
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。