前文回顾
我们先回顾一下开篇中的重点
1、C语言程序在运行时的四个重要区域:堆区、栈区、静态区、只读区
2、*操作符是个单目运算操作符,返回操作数的值所指向的地址单元。
如果你找不到开篇在简书中搜索ID:孟南知 查看更多好文
C语言指针的基本操作
如果你已经非常熟悉指针操作,可以跳过这一段看靠后的重点!
定义一个指针
声明指针我们已经在上篇中详细的说过
我们在定义一个指针的时候仅仅是在栈中声明了一个8个字节(4个字节)的变量
这个指针型变量的值是一个地址,而变量类型用于分辨从地址读取多少个字节的内容
以上内容在上篇有详细说明这里不再讨论
指针和数组的关系
我们现在来说说数组,在我的理解中数组其实可以等价于一个指针
int a[20]; a[10]=100;
int *b=(int *)malloc(sizeof(int)*20) ; b[10]=100;
cout<
上述代码我们分别用数组的方式输入和指针的方式输出
我们惊奇的发现,这两种方式的结果完全一样
这里我也不敢说这两种方式是否完全等价,但使用方法是完全等价的
对指针运算比如:p+5。这个运算是p的值加上5*指针类型长度。数组在程序中是顺序存储的。
这里我们特别说一下二维数组,二维数组实际上在内存中和一位数组的存储结构是一样的。
这里就不画图了,也就是第二行接着第一行后面继续存储。
当我们试图把一个二维数组赋值给一个指针,编译器报错了。
所以我们必须用这种方法声明。
inta[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int(*p)[4]=a;
如果我们要操作这个指针需要用到这种结构*(*(p+i)+j),其中i和j分别代表一二维下标。
多维数组在本质上和一维数组是一样的,数字的作用即是界限。
所以在多维数组的使用中,必须指定后续的界限否则与一维数组无异,编译器禁止了这种情况发生。
指针作为函数参数
我们先来说说普通变量作为参数
函数被调用后,程序在栈中新建立一个变量,在把实参的值赋值给它,其实指针作为参数是一模一样的
我们看下面这段代码
void func(int *p){
*p=66;
}
void main(){
int a; int *p=a;
func(p);
}
图-指针作为参数
最后我们输出a的结果必然是66,注意这里函数参数中的*p属于一个声明语句和*操作符无关。
我们发现,虽然我们改变了 函数体外变量的值,但是函数传参的过程和普通函数其实是一模一样的。
我们复制了实参指针变量p中的地址,新建了一个指针变量并把p的值赋值给了这个变量即形参p。
双重指针作为参数
在学习数据结构的过程中,令我最头疼的就是双重指针作为参数,让我彻底放弃C语法改用C++。
不过我们有了上面的基础,聪明的人已经明白为什么在C中要使用双重指针作为参数。
我们拿最经典的例子,即在无头结点单链表中,怎么在函数中删除第一个结点。
在看代码之前我们往回先看指针作为参数的图,如果我们不用双重指针,形参p与实参p并无任何关系
我们可以改变实参p所指向变量的值,却无法改变实参p自身的值。
#include //下面是完整代码,涉及数据结构知识
#include //printf用这不舒服,除了输入输出,并未用C++语法
using namespace std;
typedef struct List *LinkList;
struct List{
int data;
LinkList next;
};
void func(List **l){
LinkList p=(*l); //*l即是实参l本身
(*l)=(*l)->next;
free(p);
}
int main(){
List *l=(LinkList)malloc(sizeof(List));
l->data=1;
l->next=(LinkList)malloc(sizeof(List));
l->next->data=2;
l->next->next=NULL;
func(&l); //&在C中是取址符,是合法的,不能用于函数形参。
cout
return 0; } C++中的引用传参和指针究竟有什么区别 如果我们用C++语法,我们将再也不去管什么双重指针 因为我们有更好的选择就是引用传参,下面我们一张图解释什么是引用 所谓引用就是取代了传参的过程,自始至终使用的都是一个变量。 本节可能与现实情况存在出入,可以这么理解但我并不知道是否真的不会创建形参。 总结 最后我们说一句,岁月静好远离C语言,现在ARM指令集兴起,包括国产指令集也开始投入使用。 正是取代上个世纪遗产的好时候,也出现了一些新的编译型语言比如Go,现在搞编程的这么多,却都是在用和办公软件一样的的东西。 为何不想设计一个全新的编译型语言,彻底瓦解底层几乎全部用C语言的时代。