ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

初识C语言:指针核心知识

初识C语言:指针核心知识 目标1.理解指针的本质就是地址2.理解指针类型的意义能够使用正确的指针类型3.指针的运算本质就是地址的偏移4.掌握传值调用和传址调用和区别5.掌握二级指针内存和地址学习指针必须先理解内存和地址生活中我们找一个房间可以通过房间号来快速找到。计算机上CPU在处理数据的时候需要的数据是在内存中读取的处理后的数据也会放回内存中。而内存空间如何高效的管理呢就是把内存划分为一个个的内存单元每个内存单元的大小取一个字节然后给每个内存单元一个编号。在计算机里一个比特位可以存储一个2进制的位1或0一个字节空间里能放8个bit而每个内存单元也都有一个编号有了这个内存单元的编号CPU就可以快速找到一个内存空间。在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫指针。上面我们是为了方便认识对真实的内存访问进行了简化的实际上我们编写的运行在X86架构的计算机上的 C 语言程序是不会直接访问物理内存的。程序运行时为变量分配的地址在程序调试过程中我们看到的地址都是虚拟地址虚拟地址不是物理地址是虚拟地址空间中的地址。虚拟地址空间可以看作操作系统为每个程序提供的一个 “假想的、独占的、连续的内存空间”。程序中的虚拟地址会由 CPU 的内存管理单元 (MMU) 映射到物理内存中其实程序最终还是会访问物理内存的。这个由虚拟地址换成物理地址的过程需要学习到操作系统的知识。比如我们在创建一个整型变量a10内存中申请4个字节用于存放整型10其中每个字节都有地址比如这4个字节的地址分别是0x006FFD700x006FFD710x006FFD720x006FFD73如果要得到a的地址就需要取地址操作符a取出的是a所占4个字节中地址较小的字节的地址然后我们顺藤摸瓜就能访问到4个字节的数据指针变量指针变量和初始化如果指针变量是全局的默认被初始化为0也就是NULL指针如果指针变量是局部的指针变量的值是未知的这样的指针就是野指针强制使用野指针会导致非法访问内存程序崩溃指针类型inta 10;int* pa a;charch w;char* pc ch;pa左边写的是int* int*是pa的类型但是*和int应该分开理解* 说明pa是指针变量而int 说明pa指向的是整型int类型的对象。解引用操作符int main(){int a 10;int* pa a;printf(%d\n, a);printf(%d\n, *pa);return 0;}指针变量的大小int main(){printf(%zu\n, sizeof(char*));printf(%zu\n, sizeof(short*));printf(%zu\n, sizeof(int*));printf(%zu\n, sizeof(long*));printf(%zu\n, sizeof(float*));//在x64下打印都是8printf(%zu\n, sizeof(double*));//在x86下打印都是4return 0;}其实指针变量的大小是取决于数据模型的32位平台下地址是32个bit位指针变量大小是4个字节64位平台下地址是64个bit位指针变量大小是8个字节注意指针变量的大小和类型是无关的只要是指针类型的变量在相同的平台下大小都是相同的。指针存的就是内存地址编号地址是多少位指针就占多少字节char*int*double* 只是解释指向内存的方式不同指针本身占用空间一样。指针类型的意义指针解引用代码1int main(){int n 0x11223344;int* pi n;*pi 0;return 0;}代码2int main(){int n 0x11223344;char* pc (char* )n;*pc 0;return 0;}代码1会将n中的4个字节全部改为0但是代码2只是将n中的第一个字节改为0因此指针的类型决定了对指针解引用的时候有多大权限一次能操作几个字节指针加减整数int main(){int n 10;int* pi n;char* pc (char*)n;printf(n %p\n, n);printf(pi %p\n, pi);printf(pi1%p\n, pi1);printf(pc %p\n, pc);printf(pc1%p\n, pc1);return 0;}int1指针变量跳过4个字节char1指针变量跳过1个字节。所以指针类型决定了指针向前或向后走多大的距离viod*指针可以理解为无具体类型的指针泛型指针这个类型的指针可以用来接受任意类型地址但是也是有局限不能直接进行指针和整数加减和解引用运算。指针运算指针访问一堆数组int main(){int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);int i 0;for (i 0; i sz; i){printf(%d , arr[i]);}return 0;}int main(){int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);int* p arr[0];int i 0;for (i 0; i sz; i){printf(%d , *(p i));}return 0;}模拟实现strlen#includestring.hint main(){char arr[] abcdef;size_t len strlen(arr);printf(%zu\n, len);return 0;}模拟实现strlen(1)size_t my_strlen(char* s){size_t count 0;while (*s ! \0){count;s;}return count;}int main(){char arr[] abcdef;size_t len my_strlen(arr);printf(%zu\n, len);return 0;}模拟实现strlen(2)size_t my_strlen(char* s){char* str s;char* ends;while (*end){end;}return end-str;}int main(){char arr[] abcdef;size_t len my_strlen(arr);printf(%zu\n, len);return 0;}指针的关系运算地址的本质还是数值数值可以比较大小因此可以用while循环来遍历数组int main(){int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);int* p arr[0];while (p arr[sz]){printf(%d , *p);p;}return 0;}传值调用和传址调用写一个函数交换两个函数的值void Swap(int* p1, int* p2){int z 0;z *p1;*p1 *p2;*p2 z;}int main(){int a 10;int b 20;printf(%d %d\n, a, b);Swap(a, b);printf(%d %d\n, a, b);}传址调用可以让函数和主调函数之间建立真正的联系在函数的内部可以修改主调函数中的变量所以如果只是需要主调函数里的值就可以采用传值调用而如果要修改主调函数中的变量的值就需要传址调用二级指针int main(){int a 10;int* pa a;//一级指针int** ppa pa;//二级指针int*** pppa ppa;//三级指针printf(%d\n, *pa);printf(%d\n, **ppa);printf(%d\n, ***pppa);return 0;}
返回列表