字节编址换算公式,字节编程科技有限公司

  

  指针变量是C或C的一把利剑,但也是一把双刃剑。指针是变量之间关系的一种数据表达方式,在实现函数的副作用(指针变量,包括作为函数参数的函数指针)、构造链式存储(数据元素的存储位置可以是不连续的,元素之间用指针连接)、获取动态内存中必不可少。但是,指针变量与函数、数组等混在一起。具有一定的复杂性。   

  

  从“存储程序控制”的概念,第一台电子计算机Eniac成立于1945年。那时候程序没有存储在内存里,也没有控制器。这个项目是如何运作的?该程序不是现代意义上的程序,而是一个操作列表,提供了如何连接和插接电路板以组合不同硬件模块的操作指南。这种方法可以叫硬连接,也可以叫硬编程,很自然,不够自动化。正因如此,堪称全才的美国人冯诺依曼提出了“存储程序控制”的概念,增加了一个存储器和一个控制器,将数据和代码存储到存储器中,控制器依次(或根据状态控制器跳转)从存储器中读取代码,翻译指令,产生控制信号来控制计算机其他部分的运行。   

  

  或者数据代码是分块存在内存中的,通常称为段。而且代码和数据是分开存储的,也就是不存储在同一个段,各种数据分开存储在不同的段。有些数据(比如全局数据)是在程序加载的时候加载的,程序是从主函数执行的,有些数据是在需要的时候分配存储空间。   

  

  存储在内存中的数据或代码可以被随机访问(读或写)因为内存是按字节寻址的,内存就像一排长开关(有多长?如果是32位系统,则为2^32=0xFFFFFFFF),以8为一组提供一个地址(取值范围为0x00000000~0xFFFFFFFF)。而且代码和内存编译成二进制代码,加载到内存里,就好像你需要不同的按下开关。   

  

     

  

  1指针和指针变量的内涵程序和数据存入内存后,其所在内存的位置(地址)由标识符来标识,如变量、常数、函数名等。所以标识符有两层意思,地址和自身值,或者说自身地址和自身值。通常,显式使用它的值,隐式使用它的地址。隐式使用地址的变量称为指针变量,指针变量存储指针(地址),可以是变量、常数或函数名对应的地址(存储函数地址的变量称为函数指针或函数指针变量)。   

  

  看思维导图:   

  

     

  

  指针的算术运算:   

  

     

  

  指针移动:   

  

     

  

  STL的迭代器通常是容器的内部模板类,称为迭代器类。iterator类包含一个指向容器元素的指针,重载了上面的一些操作符来实现指针的移动,遍历容器中的元素。   

  

  2指针变量和const指针变量可以用const来修饰,const可以修饰自身,也可以修饰指针变量所指向的类型。   

  

  2.1 const修改指针变量本身   

  

  int n=0;int m=0;int * const p=n;p=m;//错误,p为const,2.2 const修改指针变量指向的对象无法更新。   

  

  int n=0;const int * p=n;//int const * p=n;写出来也是一样的效果n=5;* p=2;//错误,p指向一个常量,所以不能用p更新规则:const写在指针类型符号*的右边修改自身,写在左边修改它所指向的类型。   

  

  数组的类型,声明和指向数组的指针变量的类型,int arr5的声明;//标识符arr向右看是数组符号arr是5个元素的数组向左看,数组元素的类型,类型为int。它的类型可以理解为int5,也就是标识符挖出来之后剩下的东西。   

  

  当然,数组的类型可以是指针变量,比如int *,写为   

  

  int * arr5//它的类型可以理解为int*5   

  

  数组的声明是分裂式,右边部分是数组的符号和元素的个数,左边部分是元素的类型。核心在右边,所以先向右看。   

  

  声明指向数组的指针,用(*p)填充标识符:   

  

  int(* p)5;//标识符p向右看是右括号(括号的悬挂)向左看,是指针声明符号,所以P是指针。看完括号里的内容,向右看,是数组符号p指向一个有5个元素的数组向左看,数组元素的类型是int。它的类型可以理解为int(*)5,也就是标识符挖出来之后剩下的东西。   

  

  相同的int *(* p)5;//也是按照上面的思路理解的。p是一个指针,指向一个包含5个元素的数组,元素类型   

是int*。

  

英文的表达是先干先枝,好像更准确:

  

int vari<10>;

  

vari is array of int

  

int vari<10><3>;

  

vari is array of array(3 elements) of int

  

int *vari<10>

  

vari is array(10 elements) of pointer to int

  

int(*vari)<3>;

  

vari is pointer to array(3 elements) of int

  

int vari<10><3>; vari往右看,是一个数组,有10个元素,元素的类型是int<10>。

  

4 函数类型、声明与指向函数的指针变量的类型、声明将上述数据的符号<>改成(),数组元素的个数改成参数类型,则成了函数的声明,其理解规则基本一致。

  

int arr(int); // 标识符arr→往右看→是函数符号→arr是一个函数,其参数类型是int→往左看,函数返回值的类型,类型是int。其类型可以理解为int(int),也就是挖掉标识符以后剩下的内容。

  

函数元素的类型当然可以是指针变量,如int *,则写成

  

int* arr(int); // 其类型可以理解为int*(int)

  

函数的声明也是分裂式的,右边部分是函数符号及参数类型,左边部分是返回值类型。核心是右边的(),所以先往右看。

  

指向函数指针的声明,也是标识符用(*p)填充:

  

int (*p)(int); // 标识符p→往右看→是一个右括号(括号的中止)→往左看,是一个指针声明符号,所以p是一个指针,括号内的内容看完了,往右看,是函数符号→p指向一个函数,参数类型是int→往左看,函数元素的类型,类型是int。其类型可以理解为int(*)(int),也就是挖掉标识符以后剩下的内容。

  

同样的int*(*p)(int); // 也是按上述思路去理解,p是指针,指向参数类型是int的函数,函数返回值类型是是int*。

  

英文的表达是先干先枝,好像更准确:

  

int func(int vari);

  

func is function(pararmeter is int vari) returning int

  

int (*func)(int vari);

  

func is pointer to function(pararmeter is int vari) returning int

  

5 与数组、函数相关的类型与声明的理解的规则其类型和声明的写法是分裂式的。

  

对于数组,右边是数组符号、数组元素数量,数组元素的类型写在左边。

  

对于函数,右边是函数符号、函数参数类型,函数返回值类型写在左边。

  

核心是右边,以理解的规则是,从标识符开始,往右看,看是数组还是函数,及元素数量或函数参数类型,然后再往左看,对应元素类型或返回值类型。如果有括号,则先理解括号内的部分,规则也是先右后左(也可以理解为符号<>和()相对于*,有较高的优先级)。(在按上述规则理解时,如果先看到的是右括号,自然是优先级的括号,如果先看到的是左括号,则一般是函数标识符括号)。

  

  

二级指针和二维数组也可以应用于上述规则:

  

int n = 5;int *p = &n;int **pp = &p; // p先右后左,右边没有,左边首先是*,表示p是一个指针,指向的类型是int*int vari<10><3>; // vari往右看,是一个数组,有10个元素,元素的类型是int<10>。6 指针变量与数组名数组名的实质是一个指针变量,指向一块内存的基地址,数组元素的分量以其地址为基准进行偏移,如:

  

int arr<5>; arr<3> = 6; *(arr+3) = 6; // <>写法是指针写法的语法糖注意arr+3运算时,指针的移动是按arr元素的尺寸进行移动的,移动的字节数是3*sizeof(int),为了方便指针的算术运算,在C或C++编译器中,要求数组名要蜕变为指向数组首元素的指针。因为这里数组本身的尺寸是5*sizeof(int),按数组自身的尺寸移动指针没有任何意义。

  

int flag = 999; int arr<5> = {1,2,3,4,5}; arr<3> = 6; *(arr+3) = 6; // <>写法是指针写法的语法糖 //int *p = &arr; // cannot convert from 'int (*)<5>' to 'int *' int (*pp)<5> = &arr; pp++; // pp指向了整个数组的后一个元素flag(栈地址是递减分配的) int a = **pp; // a = 999 int *p = arr; p++; //p指向了数组元素的下一个元素 int b = *p; // b = 2 printf("%d %d\n",a,b); // 999 2数组名的上下文中,只有三种情况表示数组本身,其它情形都蜕变为指向数组首元素的指针。

  

int arr<3><4> = {{1},{2},{3,10,11,12}}; // 情形一,声明时 int n = sizeof(arr); // 情形二,使用sizeof时 int (*parr)<3><4> = & arr; // 情形三,取址时 int (*parr)<4> = arr; // 其它情况都蜕变为指向数组首元素的指针 int a = *(*(arr+2)+3); // arr<2><3>是指针写法的语法糖 //int **pp = arr; // cannot convert from 'int <3><4>' to 'int ** ' // pp和arr类型完全不一致,这的类型是int**,arr的类型是int<3><4>,蕴含有长度信息。 printf("%d\n",arr<2><3>); // 12 printf("%d\n",a); // 12数据与指针的等价关系:

  

  

比较一下指针和数组:

  

  

7 指向数组或函数的指针变量的使用情形主要用做函数参数。

  

7.1 数组指针用作函数参数

  

#include <stdio.h>#include <stdlib.h>void func(int(*p)<4>){}void func2(int**pp){}int main(){ int arr<3><4> = {{1},{2},{3,10,11,12}}; int (*p)<4> = arr; func(p); func(arr); int **pp = (int**)malloc(sizeof(int*)*3); func2(pp); return 0;}一元数组和二元数组用做函数参数:

  

#include <stdio.h>#include <iostream>using namespace std;void func0(int arr<10>){}void func2(int arr<>){}void func3(int *arr){}void func4(int *arr<20>){}void func5(int **arr){}void oneDimension(){ int arr<10> = {0}; int *arr2<20> = {0}; func0(arr); func2(arr); func3(arr); func4(arr2); func5(arr2);}void test0(int arr<3><5>){}void test2(int arr<><5>){}void test3(int (*parr)<5>){}void twoDimension(){ int arr<3><5>={0}; test0(arr); test2(arr); test3(arr);}int main(){ oneDimension(); twoDimension(); return 0;}7.2 函数指针用作函数参数

  

#include <stdio.h>bool comp(int a,int b){return false;}void sort(int(*p)<4>,bool(*pf)(int,int)){}int main(){ bool(*pf)(int,int) = comp; int arr<3><4> = {{1},{2},{3,10,11,12}}; int (*p)<4> = arr; sort(p,pf); sort(arr,pf); return 0;}使用总结:

  

  

8 数组与函数指针变量其元素或返回值混合声明的理解直接看代码和备注:

  

int(*fp)(int); // 函数指针int * arr<5>; // 指针数组int(*fa<5>)(int); // 函数指针数组,(*fa<5>)要用括号,返回类型int(int)要分裂int(* fpp())(int); // 函数指针函数,(* fpp())要用括号,返回类型int(int)要分裂int(* fpa())<5>; // 数组指针函数,(* fpa())要用括号,返回类型int要分裂int(* fppp(int(*fp2)(int a)))(int b); //函数fppp有一个函数指针做参数,返回一个函数指针// 函数可以返回函数指针或数组指针,不能返回函数或数组// 当先从fp2开始理解时,发现其包括在一个()中,所以是一个函数参数// 当先从fppp开始时,发现其是一个指针,所以当一个声明中有多个标识符时,从最左边的一个开始int(*parr<10>)<5>; // 数组指针数组以上代码可以看到,写函数指针函数(一个函数返回一个函数指针)或数组指针函数(一个函数返回一个数组指针)时,需要用到括号来标明优先级,然后将函数的返回类型与参数(或数组的元素类型与数组的元素个数)分开写到括号的前后。

  

9 二级指针应用的场合当需要一个主调函数调用一个被调函数来修改主调函数内或全局的指针变量时,被调函数需要使用一个二级指针或一个指针引用

  

int n = 5;int m = 6;int *p = &n;int *p2 = &m;int **pp = &p; // p先右后左,右边没有,左边首先是*,表示p是一个指针,指向的类型是int*int **pp2 = &p2;int *&r = p;void func1(int *p){ p = &m; // 如果指向的不是动态内存,函数调用结束后p本身(局部变量)将不复存在 *p = 55; // 函数内解引用指针变量参数,产生副作用,更新p指向的变量的值为55}void func2(int** pp){ pp = pp2; // 如果指向的不是动态内存,函数调用结束后p本身(局部变量)将不复存在 *pp = p2; // 函数内解引用指针变量参数,产生副作用,更新pp指向的变量的值为p2(一级指针)}void func3(int*&r){ r = p2; // 引用自动解引用}另外,二级指针也用于动态内存分配场合:

  

int **arr = (int**)malloc(sizeof(int*)*n); for(int i=0;i<n;i++) arr = (int*)malloc(sizeof(int)*n);ref

  

达尔教育 陈宗权 https://www.bilibili.com/video/BV1XT4y1Z7GK?p=20

  

鹏哥C语言 https://www.bilibili.com/video/BV1TT4y1F7Z9?p=123

  

-End-

相关文章