1.内存和地址
内存:是计算机用于存储和访问数据的硬件设备,于临时存储和读取数据、指令和程序。
CPU与内存的关系:CPU则是计算机的中央处理器,负责执行程序指令、进行算术和逻辑运算等操作。它从内存中读取指令和数据,并对其进行处理和操作。
地址:CPU如何访问内存进行执行指令和处理数据?答案是通过地址(指针)。
总结:CPU通过地址访问内存,在特定的地址读取数据或指令,并在需要时将结果写回内存
2.指针变量和地址
指针变量:
有int* a ,double*b,变量类型+*+变量名;——>*表示这个变量是指针
而前面的int....表示这个指针变量指向的类型 int *a = &10
这里创建了一个a指针变量,a中存放的值是10的地址
地址:
指针变量的地址大小取决于你的电脑是32位还是64位
**# c语言指针的作用:
-
内存管理:指针可以用于动态分配和释放内存。通过使用函数如malloc()和free(),可以在运行时动态地分配和释放内存块,这在处理不确定大小的数据结构或需要灵活管理内存的情况下非常有用。
-
数组:指针与数组的关系非常密切。在C语言中,数组名本质上是一个指向数组第一个元素的指针。通过使用指针算术或指针运算,可以遍历数组元素并对其进行操作。
-
函数传递和返回:指针可以用于在函数之间传递数据或者允许函数修改调用方提供的变量。通过传递指针作为参数,函数可以直接修改原始数据,而不是通过值拷贝进行操作。这对于需要交换大量数据或在函数内部修改外部变量时非常高效。
-
动态数据结构:指针允许创建和操作动态数据结构,如链表、树和图。通过使用指针,可以连接不同的节点或对象,并在运行时根据需要进行链接和断开,以实现灵活的数据结构。
-
访问硬件和底层操作:指针在与硬件或进行底层操作的库进行交互时非常有用。通过将指针与特定地址相关联,可以直接读取或写入内存中的数据,以实现对硬件寄存器、设备驱动程序和底层操作系统功能的访问。
3.指针变量类型的意义
指针a的地址大小是不变的,那要一个不同类型的指针有什么用?
如果int a =10; int* p = &a;已经有了a的类型定义,那么指针类型变量是不是多余了?
此时,我们将int* p =&a;改为char*p= &a;由于a是int,占4个字节,char是占一个字节。
那么*p = 0;指令后只能改变1字节的大小,而不是4个字节。
总结:指针类型决定了指针解引用时一次能操作多少字节
4.const 修饰指针
为什么要用:使用const
修饰指针的主要目的是为了约束指针的操作和保护被指向的数据。
const int* p = &a 意味着不能通过p指针改变a的值
int * const p = &a 意味着不能改变p指针指向的地址
5.指针运算
三种:1.指针+-整数:得到一个跳过该指针类型大小数目多少的地址
如int a=0; int * b = a; b+2就是b的地址加上两个int的大小;
假如改成 char *b = a;b+3就是b的地址加上两个char的大小;
2.指针-指针:可以对两个指针进行减法运算,以计算它们之间的位置差。结果是一个表示 相对偏移量的整数值。例如,假设有两个整型指针intPtr1
和intPtr2
,我们 可以计算它们之间的位置差:int offset = intPtr2 - intPtr1;
。offset
的 值将是一个整数,表示intPtr2
相对于intPtr1
向后移动了多少个整型大小的 内存位置。
3.指针关系运算:比较地址的大小
6.野指针
是什么:
是指指向已释放或无效内存的指针。当一个指针指向已经被释放的内存块、超出作用域 的局部变量、或者没有被初始化的指针时,它就成为野指针。
如int*p;
危害:
- 未定义行为:对野指针进行解引用或使用其值,会导致未定义行为。这可能会导致程序崩溃、数据损坏或其他不可预料的结果。
- 安全问题:野指针可能成为安全漏洞的源头。恶意用户可以利用野指针来执行代码注入、访问敏感数据或者绕过安全检查。
- 调试困难:野指针的存在会增加程序调试的难度,因为导致问题的原因可能不明显,难以追踪。
避免措施:
- 初始化指针:在定义指针时,立即将其初始化为一个有效的内存地址或者NULL。这样可以确保指针不会成为野指针。
- 及时释放指针:当指针不再需要时,要及时释放相关的内存,并将指针置为NULL。这可以防止出现指向已释放内存的野指针。
- 避免悬挂指针:确保指针在被使用之前,其所指向的对象是有效的。尤其是在函数调用或对象销毁之后,要避免对指针进行解引用
7.assert 断言
作用:assert宏用于检查一个条件的真实性,并在条件为假时触发断言失败。断言失败时,它会将 错误消息打印到标准错误输出(stderr)并终止程序的执行。
使用方法:#include <assert.h> ... assert(x > 0); // 如果x小于等于0,则触发断言失败
8.数组名,字符串理解:
int arr[] = {23,43,4,5}; arr就是数组首元素的地址
char* a= "adfoeh"可以理解为一个存放字符的数组,a是首元素的地址
可通过arr++;a++;获得后面元素的地址,再通过解引用*arr获得该地址的值
9. 二级指针:
一级指针是某个变量的地址 如 int* p = &a;
那么二级指针是指向一级指针地址的指针 如:int* *ptr = &p;
其中int* 是该指针指向变量类型,中间的*则是表示这是一个指针变量
更高级的指针是同理的
10.指针数组:
是什么:是一个存放元素类型为指针的一个数组 int arr[10] 是存放int 类型;int* arr[10]则是存放类 型为整形指针。
11.指针数组模拟二维数组:
二维数组可以看做是一个一维数组,其中的元素是一个一维数组。那么将每个数组元素改为一个指向一维数组的指针,就可以模拟为二维数组
arr[i][j] --- > *(*(arr+i)+j)
解释:
- arr是该一维数组的数组名,也是首元素的地址。
- arr+i 表示跳过i个arr大小的元素地址,即 arr+i 指向了一维数组中的第i个元素。//arr大小相当于一个一维数组
- *(arr+i) 是对指针 arr+i 进行解引用,表示取得 arr+i 所指向的值,即一维数组中的第i个元素的地址。
- *(arr+i)+j 表示在一维数组中的第i个元素的地址上,跳过j个arr[i]大小的元素地址,即指向一维数组中的第i行第j列的元素。
- 最终,*(*(arr+i)+j) 是对 *(arr+i)+j 所指向的地址进行解引用,表示取得 arr[i][j] 的值。
注意三个不同概念的区分,arr 、arr[0]、&arr
arr: arr 是一个数组的名称,也是数组的首元素的地址。它代表整个数组的起始位置。
1.arr[0]: arr[0] 表示数组中的第一个元素。它是通过下标访问数组元素的一种方式。例如,arr[0] 表示数组中的第一个元素,arr[1] 表示数组中的第二个元素,以此类推。
2.&arr: &arr 表示整个数组的地址。它返回数组的指针,指向整个数组的起始位置。这个指针可以用来传递数组给函数或在程序中进行其他操作。
3.简单来说,arr 表示整个数组的起始位置,arr[0] 表示数组中的第一个元素,而 &arr 表示整个数组的地址。
12.数组指针变量:
是什么:一个指向数组的指针。问题来了,数组的地址是什么?如何通过指针表示一个有着多个 元素大小的数组?
int (*p)[10] 解释:int [10] 指针变量类型;*表示指针,[10]表示指向数组的大小
p+1地址是多少? 是p跳过10*4字节的地址
13.函数指针:
函数地址在哪:函数的地址会放到一个寄存器里,表示用函数名即可,如void eklw(int a,double)
函数地址就是eklw
形式:void(*p)(int ,double) 表示void 可以是函数的返回类型,int,double 是函数参数类型
*表示指针,p是指针变量名
作用:
-
回调函数:可以将函数指针作为参数传递给其他函数,使得被调函数可以在需要时调用传入的函数指针,实现不同函数间的灵活交互。
-
运行时动态选择函数:根据不同的条件或情况,在运行时选择不同的函数执行,而不需要在编译时固定函数调用。
总结:就是一个中介,可以将多个函数包装起来,实现某一特定功能
14.qsort库函数
是什么:qsort
是 C 语言标准库中提供的用于排序数组的函数。它的使用需要提供以下内容:
怎么用:
- 数组的起始地址。
- 数组中元素的个数。
- 每个元素的大小(以字节为单位)。
- 比较函数的指针,用于指定元素的比较方式。
void qsort(void* base, size_t num_elements, size_t element_size, int (*compare_function)(const void*, const void*));
第四个就是回调函数,需要自己定义
#include <stdio.h> #include <stdlib.h>
// 比较函数
int compare(const void* a, const void* b)
{ int num1 = *(int*)a;
int num2 = *(int*)b;
if (num1 < num2) return -1;
if (num1 > num2) return 1;
return 0; } i
nt main()
{ int nums[] = {5, 2, 8, 1, 0, 3};
int num_elements = sizeof(nums) / sizeof(nums[0]);
printf("Before sorting:\n");
for (int i = 0; i < num_elements; i++)
{ printf("%d ", nums[i]); }
printf("\n");
qsort(nums, num_elements, sizeof(nums[0]), compare);
printf("After sorting:\n");
for (int i = 0; i < num_elements; i++)
{ printf("%d ", nums[i]); }
printf("\n");
return 0; }
15.sizeof和strlen
//int main()
//{
// int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据
//
// printf("%zd\n", sizeof(a));//16 - sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
// printf("%zd\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - 4/8
// //int*
// printf("%zd\n", sizeof(*a));//a表示的就是数组首元素的地址,*a 就是首元素,大小就是4个字节
// printf("%zd\n", sizeof(a + 1));//a表示的就是数组首元素的地址,a+1就是第二个元素的地址,这里的计算的是第二个元素的地址的大小-4/8
//
// printf("%zd\n", sizeof(a[1]));//a[1]是数组的第二个元素,大小是4个字节
// printf("%zd\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小就是4/8个字节
// //int (*pa)[4] = &a
// //int(*)[4]
// printf("%zd\n", sizeof(*&a));//sizeof(a) -16
// //1. & 和 * 抵消
// //2.&a 的类型是数组指针,int(*)[4],*&a就是对数组指针解引用访问一个数组的大小,是16个字节
//
// printf("%zd\n", sizeof(&a + 1));//&a+1是跳过整个数组后的一个地址,是地址,大小就是4/8个字节
//
// printf("%zd\n", sizeof(&a[0]));//&a[0]是数组第一个元素的地址,大小就是4/8个字节
// printf("%zd\n", sizeof(&a[0] + 1));//&a[0] + 1 是第二个元素的地址,大小就是4/8个字节
// //int*
// return 0;
//}
//
//int main()
//{
// char arr[] = { 'a','b','c','d','e','f' };//arr数组中有6个元素
//
// printf("%d\n", sizeof(arr));//计算的是整个数组的大小,6个字节
// printf("%d\n", sizeof(arr + 0));//arr+0 是数组第一个元素的地址 4/8
// printf("%d\n", sizeof(*arr));//*arr是首元素,计算的是首元素的大小,就是1个字节
// printf("%d\n", sizeof(arr[1]));//arr[1] - 1
// printf("%d\n", sizeof(&arr));//4/8
// printf("%d\n", sizeof(&arr + 1));//4/8
// printf("%d\n", sizeof(&arr[0] + 1));//4/8
//
// return 0;
//}
//
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
//'a'-97
//printf("%d\n", strlen(*arr));//err
// //'b'-98
//printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
return 0;
}
总结:一维数组的sizof(arr)是整个数组大小sizeof(&arr)
只要是地址,就是4或者8
&arr是整个数组大小,+1直接跳过整个数组