指针前言:&运算符取得变量得地址

int i;

scanf("%d",&i);

这里面的&是一个运算符,它的作用是取得变量的地址,它的操作数必须是变量

为什么变量会有地址,因为C语言的变量是放在内存里的,我们之前用sizeof可以看到某个变量所占据的字节

我们举个例子,如果我们在printf中使用&是不是就能输出地址呢?

int i;

printf("00%x",&i); //地址是以十六进制形式存储,所以我们用%x来打印十六进制的数

//输出结果为0061fecc

但是这个代码会有一个warning,实际上如果我们想要让printf输出地址,我们要用的是%p

int i;

printf("%p\n",&i);

//输出结果为0061fecc

我们也可以这么试试

int i;

int p;

p=(int)&i; //将&i,即i的地址强制类型转换为int之后再赋值给p

printf("00%x",p);

//输出结果为0061fecc

但是这个结果在不同架构的编译器上结果不同

比方说在32位架构下int和&i都占据4个字节,但是在64位架构下int占据4个字节,而&i占据8个字节,内存的不同就一定导致了地址的不同

地址的大小是否与int相同取决于编译器

所以我们要让printf输出地址,我们要用的是%p,而不是真的把它当成整数,地址和整数并不永远是相同的

int i;

printf("%p",&i);

&不能取的地址

&不能对没有地址的东西取地址,它必须是对一个明确的变量取地址

例如

&(a+b)

&(a++)

&(++a)

上述类型都是不对的

试试这些&

相邻变量的地址

int i;

int p;

printf("i的地址是%p\n",&i);

printf("p的地址是%p\n",&p);

//输出结果为

//i的地址是0061fecc

//p的地址是0061fec8

我们可以针对这个输出结果来分析一下,c在十六进制中是12,12-8=4,所以i和p之间相邻4个字节,正好是一个int所占据的类型,所以i和p是在内存中是相邻存储的

数组的地址

int a[10];

printf("%p\n",&a);

printf("%p\n",a);

printf("%p\n",&a[0]);

printf("%p\n",&a[1]);

//输出结果为

//0061fea8

//0061fea8

//0061fea8

//0061feac

我们会发现&a和a以及a[0]的地址是相同的,而a[1]由于是和a[0]相邻的,所以它们之间相差4个字节

但实际上,这三种取地址的方式虽然地址输出结果一样,但其根本是有所区别的

int a[6]={1,2,3,4,5,6,};

printf("%p\n",a);

printf("%p\n",&a);

printf("%p\n",&a[0]);

//输出结果

//0061feb8

//0061feb8

//0061feb8

此时输出地址会是一样的

但当我们分别对a、&a、&a[0]进行+1的操作,情况就有所不同了

int a[6]={1,2,3,4,5,6,};

printf("%p\n",a+1);

printf("%p\n",&a+1);

printf("%p\n",&a[0]+1);

//输出结果

//0061febc a+1 相较于a 加了4字节

//0061fed0 &a+1 相较于&a 加了24字节

//0061febc &a[0]+1 相较于&a[0] 加了4字节

a和&a[0]的类型都是指向数组首元素的指针,所以他们的+1就是指向数组内的下一个元素

而&a的类型是指向整个数组,所以它的+1会指向整个数组的下一个位置,上述数组例子中有6个int元素,于是加了4*6=24个字节

因此,虽然取得的数组地址相同,但是它们的步长不同,导致了指向的位置不同。

指针

如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在函数内访问这个变量?

我们在scanf里面通过&可以取到变量的地址,但我们最终看到的是这个变量的值而非地址,我们之前试过,如果把这个地址交给一个整数,这件事是不靠谱的,因为整数和地址不见得永远是相同的类型,说明在scanf内部一定存在着某种可以通过地址来访问变量并取得变量值的一个东西

我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量呢?它就是指针

指针:就是保存地址的变量

int i;

int* p=&i; //将变量i的地址储存在指针变量p中

这个*,它可以靠近int,也可以靠近变量,都是可以的

int* p=&i;

int *p=&i;

定义变量,有一个老生常谈的问题

int* p,q;

int *p,q;

//这两行代码是等价的,意思都是定义一个指针变量p和一个普通的int变量q

如果你想要定义两个指针变量,并且非要写在一行代码内,那么应该这么写

int *p,*q;

指针变量

指针变量的值是内存的地址

普通变量的值是实际的值

指针变量的值是具有实际值的变量的地址

作为参数的指针

当我们把指针作为函数参数时,可以这样定义

void f(int *p);

所以我们在调用这个函数的时候,我们就要交给它一个地址,而不能交给它那个变量本身

int i=0;

f(&i);

而不能是

int i=0;

f(i); //错误的写法

所以我们可以通过传进函数内的这个变量地址,实现在函数里面通过指针访问(读和写)函数外部的变量i

void f(int *p);

int main(void){

int i=6;

f(&i);

return 0;

}

void f(int *p)

{

printf("%p\n",p); //通过这种方法来读取p的值,即i的地址

}

我们之前在函数中提到过,函数中传递的是变量的值而非变量,所以无论我们在函数内部对变量做什么处理,它都与外部无关

但是我们通过指针,使得f函数里面拥有访问外部变量i的能力了,读取我们已经示范过了

那么我们要怎么写入呢?即如果在函数内部实现对外部变量的修改?

解引用操作符*

*,又称间接访问操作符,它是一个单目运算符

它会通过指针来间接访问指针所存储地址上的数据

int i=6;

int *p=&i;

printf("%d\n",*p);

//输出结果为6,*p作为一个整体,你可以就把它看作一个整数

当然,它可以做右值也可以做左值,所以我们就可以这样来修改外部变量i的值

*p=666;

也可以将它用作右值赋给其他变量

int k=*p;

我们在讲函数的时候一直在说,函数调用时发生的参数转移,是一种值的传递,函数的参数和调用它的地方没有任何的联系

但现在情况有所不同,我们仍然坚持这个时候函数发生的是值的传递,只不过这个值是一个地址,但是因为传进来的值是地址,所以通过这个地址在函数内部,我们可以去访问到外面的变量i

scanf中不加&编译会报错吗?

int i=6;

scanf("%d\n",i);

整数和地址是一样大的,你把一个整数传进去和把一个地址传进去,对于scanf来说是没有区别的,它以为你传进去的是i的地址,实际上你传进去的是i的值,然后将i的值当作地址来执行,但是这个时候就容易出现问题导致代码崩溃

所以不带&,编译不见得会报错,但是运行一定会出错

指针有什么用

应用场景一

交换两个变量的值

void swap(int *pa,int *pb)

{

int t=*pa;

*pa = *pb;

*pb = t;

}

应用场景二

函数需要返回多个值,某些值就只能通过指针返回

此时传入的参数实际上是需要保存带回的结果的变量

比方说有一个数组,我需要找出数组中的最大的单元和最小的单元,这时我们需要返回两个值

void minmax(int a[],int len,int *max,int *min); //len是数组长度

int main(void)

{

int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};;

int min,max;

minmax(a,sizeof(a)/sizeof(a[0]),&max,&min);

printf("min=%d,max=%d\n",min,max); //这里体现出minmax函数返回了min和max两个值

return 0;

}

void minmax(int a[],int len,int *max,int *min) //判断哪个数组单元是max和min

{

int i;

*min=*max=1;

for(i=1;i

if(a[i]<*min){

*min=a[i];

}

if(a[i]>*max){

*max=a[i];

}

}

}

指针最常见的错误

定义了指针变量,还没有指向任何变量,就开始使用指针

int i;

int *p; //我们没有让指针指向任何变量

i=12;

*p=20; //这里想的是,我们既然能对i赋值,那我们能不能也对*p赋值呢

结果是不行,当我们没有让指针指向任何变量时,p里面可能是一团乱七八糟的东西,如果这个时候你把这团乱七八糟的东西当作地址,它可能会指向一片莫名其妙的地方,有可能这个地方是很重要的地方,而你尝试通过*p去赋值就有可能使程序崩溃

当然,我们不见得每次都会崩溃,但是总有一天会出事的,所以不要这样使用指针

一个指针变量没有得到任何变量的实际地址之前,你不能通过它用*去访问任何的变量

指针与数组

如果把数组作为函数参数传入函数,那么在函数内部接收到的是什么东西呢?

我们知道如果传一个普通变量,参数接收到的是值;如果传一个指针,参数接收到的也是一个值,只不过这个值是一个地址

而且我们前面也提到过当我们把数组当作参数传入函数,我们一般要再设置一个变量来表示数组的长度,而不能直接把sizeof(a)/sizeof(a[0])作为参数来表示数组长度,为什么呢

我们拿之前的minmax代码来做点示范,我们首先在main函数和minmax函数中各自输出sizeof(a)看看到底a占多少字节

void minmax(int a[],int len,int *max,int *min);

int main(void)

{

int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};;

int min,max;

minmax(a,sizeof(a)/sizeof(a[0]),&max,&min);

printf("min=%d,max=%d\n",min,max);

printf("%d\n",sizeof(a)); //结果为68

return 0;

}

void minmax(int a[],int len,int *max,int *min)

{

int i;

*min=*max=1;

printf("%d\n",sizeof(a)); //结果为4,这刚好和一个指针,也就是一个地址的大小是一样的

for(i=1;i

if(a[i]<*min){

*min=a[i];

}

if(a[i]>*max){

*max=a[i];

}

}

}

并且我们运行会有一句warning

[Warning] ‘sizeof’ on array function parameter ‘a’ will return size of ‘int *’ [-Wsizeof-array-argument]

[警告] 对数组函数参数“a”使用“sizeof”将返回“int *”的大小 [-Wsizeof-array-argument]

意思是,函数参数中数组的sizeof返回的是int *的sizeof,而不是int a的sizeof。换句话说就是函数参数中的那个array(数组)其实是int *

我们还可以分别在main和minmax函数中看看a的地址是什么

printf("main %p\n",a);

printf("minmax %p\n",a);

//输出结果都是正确且相同的

综上这些都说明,其实minmax里面的数组a就是main当中的数组a,它们不是一模一样的问题,而是同一个

所以如果当我们在minmax函数中去修改某个特定数组单元的值时,会发现main当中对应的那个数组单元的值也被改变了

void minmax(int a[],int len,int *max,int *min);

int main(void)

{

int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};;

int min,max;

minmax(a,sizeof(a)/sizeof(a[0]),&max,&min);

printf("min=%d,max=%d\n",min,max);

printf("a[0]=%d\n",a[0]); //输出显示a[0]=1000

return 0;

}

void minmax(int a[],int len,int *max,int *min)

{

int i;

*min=*max=1;

a[0]=1000; //修改了a[0]的值

for(i=1;i

if(a[i]<*min){

*min=a[i];

}

if(a[i]>*max){

*max=a[i];

}

}

}

实际上**,函数参数表里的数组就是指针,sizeof(a)==sizeof(int )*

这也就是为什么我们在函数参数表的数组必须留一个空的方括号[],即便你在方括号中写数字也没用(如a[2]),为什么在函数里面没法用sizeof来得到正确的数组的元素个数了,原因就在于,它是个指针

那既然它实际上是个指针,我们也可以用指针的方式来写,而非数组的形式

void minmax(int *a,int len,int *max,int *min)

修改后我们会发现编译通过了,并且运行结果完全正确,甚至warning都没有了

可以用数组的运算符[]进行运算

你可能会想,虽然它在函数参数表中是个指针,但是我们在函数内还是在使用如a[i],a[0]这种来表示单独的数组单元,我们是可以这样用的

所以似乎数组和指针存在某种联系

数组参数

以下四种函数原型是等价的(在参数表中)

int sum(int *ar,int n);

int sum(int *,int );

int sum(int ar[],int n);

int sum(int [],int);

数组变量是特殊的指针

数组变量本身表达地址,所以我们在取数组地址的时候,即便不加&符号也可以

int a[10];

int *p=a; //无需用&

但是数组的单元表达的是变量,需要用&取地址

int a[10]={2};

int *p=&a[0];

同时我们知道a的地址就等于a[0]的地址

a==&a[0]

[]运算符可以对数组做,也可以对指针做

p[0]<==>a[0]

我们还是拿minmax那个函数来举个例子

void minmax(int a[],int len,int *max,int *min);

int main(void)

{

int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};;

int min,max;

minmax(a,sizeof(a)/sizeof(a[0]),&max,&min);

printf("min=%d,max=%d\n",min,max);

printf("a[0]=%d\n",a[0]); //输出显示a[0]=1000

int *p=&min;

printf("*p=%d\n",*p);

printf("p[0]=%d\n",p[0]); //这里我们对指针变量使用p[0],最终会发现结果和*p是一样的

return 0;

}

void minmax(int a[],int len,int *max,int *min)

{

int i;

*min=*max=1;

a[0]=1000; //修改了a[0]的值

for(i=1;i

if(a[i]<*min){

*min=a[i];

}

if(a[i]>*max){

*max=a[i];

}

}

}

我来大概解释一下p[0]是怎么一回事,我们现在知道min的值是2,并且我们有一个指针变量p指向min,那么*p就是min的值,p[0]就是把p所指向的地址当作是一个数组,虽然那个地址,也就是min,并不是一个数组,然后我们把min的值看作是p[0]。

你可以理解为min其实是int min[1],然后它的有效下标只有一个min[0],当然普通变量不能这么写,我们只是举个例子,不过指针变量是可以这么写的,例如p[0]

*运算符可以对指针做,也可以对数组做

int a[10];

*a=25; //*a实际上也就是a[0]的值

数组变量是const的指针,所以不能被赋值

我们在讲数组的时候提到过,如果有两个数组,我们不能直接让他们交换

int a[];

int b[];

b=a; //这事是不行的

甚至就是在定义的时候也不能说b=a

int a[];

int b[]=a; //这也是不行的

数组变量之间是不能做互相赋值的

把它传递到参数,实际上做的是

int *q=a;

你可能会想,在参数表中,b[]是指针,*p也是指针,为什么一个可以赋值,一个不可以

区别就在于,int b[]可以看作这样

int b[]---->int * const b;

意思是b是一个常数,不能被改变,你初始化创建了这个数组,它是这个数组,它就不能再去代表别的数组了,所以它是一个常量指针

(就是定义一个数组产生的这个数组会有电脑分配一个指定的地址,指向的地址就不能再改了,但你用指针指向某个地址仍然可以改变指针使其指向别的地址)

指针和const(C99)

我们知道const是一个修饰符,它加在变量前面是说这个变量不能被修改

指针是一种变量,在指针里面有两个东西,一个是指针本身,一个是指针所指的那个变量

所以指针本身可以是const,所指向的变量也可以是const

指针是const

表示一旦得到了某个变量,不能再指向其他变量

int * const q =&i; //q是const

*q=26; //OK,所指向的i的值可以修改,因为i不是const

q++; //ERROR,但是指针的值,即指针所指向的地址不能修改

所指是const

表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)

const int *p =&i;

*p=26; //ERROR,*p是const,但是i不是

i=26; //OK,因为i不是const,所以我们可以修改i的值,但是不能通过*p来修改i的值

p=&j; //OK,指针的指向没有锁,我们可以改变p指向的地址

const与指针结合,只有两种意思,要么是指针不可修改,要么是不可通过指针修改

来判断一下

int i;

const int *p1=&i; //

int const *p2=&i; //这两种形式是一样的,都是const在*前面,即不可通过指针修改

int *const p3=&i;

判断哪个被const了的标志是const在*的前面还是后面

const在*的前面,表示不可通过指针修改

const在*的后面,表示指针不可修改

指针只有2个能力,一是指向,二是通过指针去修改值

2个const,一个把指向锁了,一个把修改能力锁了

转换

我们总是可以把一个非const的值转换成const的

void f(const int *x);

这个时候我们给了一个非const其实也没问题

int a=15;

f(&a); //OK

因为在函数参数中定义const的指针实际上是一种保证,保证在函数内部不会去动这个指针所指的值

当然给个const肯定没问题

const int b=a;

f(&b); //OK

当要传递的参数的类型比地址大时,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

我们现在可能想不出来有什么比地址还大,我们后面讲到结构的时候会知道,我们可以自己定义结构,结构里面可以很复杂,可以有很大的东西,我们就会用指针传进去,但是我们又害怕传进去之后你对我外面的变量做修改,因此传一个const结构的指针给你

const数组

const int a[]={1,2,3,4,5,6,};

数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int

所以必须通过初始化进行赋值,不然后面你再赋值会因为const的原因无法修改数组单元

保护数组值

因此把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值

为了保护数组不被函数破坏,可以设置参数为const

int sum(const int a[],int length);

指针运算

1+1=2,但我们如果对指针+1会发生什么呢?

我们可以分别用char和int指针来做个实验

char ac[]={0,1,2,3,4,5,6,7,8,9,};

char *p=ac;

printf("p =%p\n",p);

printf("p+1=%p\n",p+1);

int ai[]={0,1,2,3,4,5,6,7,8,9,};

int *q=ai;

printf("q =%p\n",q);

printf("q+1=%p\n",q+1);

p =0061febe

p+1=0061febf //p到p+1,确实是只加了1

q =0061fe94

q+1=0061fe98 //而q到q+1加了4

这是因为,p是char类型,q是int类型,而sizeof(char)=1,sizeof(int)=4

所以我们对指针加1,加的是指针对应类型的一个单元的长度,即sizeof

我们前面在讲到数组和指针的关系时,我们提到过当把一个数组赋给一个指针后,可以拿那个指针做像数组一样的操作,例

*p=ac[0];

*(p+1)=ac[1]; //这里加括号是因为:*是单目运算符,优先级比+高

*(p+n)<====>ac[n];

此时给指针加1表示要让指针指向下一个变量

如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

我们已经知道指针是可以加一个整数的,除此之外我们还可以

给指针加、减一个整数(+,+=,-,-=)

递增递减(++/–)

两个指针相减(此时结果不是两个地址的差,而是两个地址之间隔了多少个sizeof)

*p++

取出p所指的那个数据来,完事之后顺便把p移到下一个位置去

*的优先级虽然高,但是没有++高,所以我们不用加括号

常用于数组类的连续空间操作

在某些CPU上,这可以直接被翻译成一条汇编指令(所以这也是为什么C语言早期要保留+±-这种运算符的原因,因为可以直接转换成汇编指令,运行效率高)

我们通过数组遍历这件事来试试

正常情况下我们会这样写

int a[]={0,1,2,3,4,5,6,7,8,9,};

for(int i=0;i

printf("%d ",a[i]);

}

当我们使用*p++,可以把代码改成这样

int a[]={0,1,2,3,4,5,6,7,8,9,-1}; //这里加的-1是判断条件

int *p=a;

for( ;p!=-1;){

printf("%d ",*p++);

}

当然如果for循环是这样,我们可以改成while

int a[]={0,1,2,3,4,5,6,7,8,9,-1};

int *p=a;

while(p!=-1){

printf("%d ",*p++);

}

我们在学到字符串时,会大量看到这样的运算,到时候我们再仔细探究*p++到底有什么意思

指针比较

< <= == > >== !=都可以对指针做

比较它们在内存中的地址(地址大小)

数组中的单元的地址肯定是线性递增的

(指针没有乘除,没有意义)

0地址

现代的操作系统,无论是Windows、Marcos、Linux还是其他的Unix,这些操作系统都叫做多进程的操作系统,它的基本管理单元叫做进程

每一个进程运行起来后,操作系统会给它一个虚拟的地址空间,也就是说所有的进程在运行起来时都以为自己具有从0开始的一片连续的空间,任何一个进程里面都有0地址(虚拟的,具体是什么得看操作系统那门课)

内存中当然有0地址,但是0地址通常是个不能随便碰的地址

所以指针不应该具有0值

因此可以用0地址来表示特殊的事情

例如我们可以在指针变量定义时先赋给它一个0,如果我们后面没有对指针变量进行真正的初始化,0地址的指针就会让代码崩溃。好处是我们可以一眼查出来指针没有进行真正的初始化。

NULL是一个预定义的符号,表示0地址,我们建议以后要用0地址的时候用NULL来表示,因为有的编译器不愿意你用0来表示0地址

指针的类型

无论指向什么类型,所有的指针的大小都是一样的,因为都是地址(这里指的是指针所占的字节大小,而非地址大小)

但是指向不同类型的指针是不能互相赋值的,这是为了避免用错指针。当然如果你想,你可以做强制类型转换(作为初学者,我劝你不要轻易尝试这个事情),但我们后面讲这个是因为我们想带出另一个东西:void*

指针的类型转换

void* 表示不知道指向什么东西的指针(这个往往会用在某个底层系统程序里,我要直接去访问某个内存地址所代表的如外部设备、控制寄存器等)

计算时与char*相同(但不相通)

指针也可以转换类型

int *p=&i;

void *q=(void*)p; //把int的p强制转换成void*后赋值给q

但是这并没有改变p所指的变量的类型(i还是int),而是让后人用不同的眼光通过p看它所指的变量(也就是我们通过p或q去看i的时候,我不再当你是int,我就认为你是个void)

总结:用指针来做什么

需要传入较大的数据时用作参数

传入数组后对数组做操作

函数返回不止一个结果

用函数来修改不止一个变量

动态申请的内存

动态内存分配

输入数据时,如果先告诉你个数,然后再输入,要记录每个数据

C99可以用变量做数组定义的大小

int number;

scanf("%d",&number);

int a[number]; //C99

那C99之前呢,这时就必须使用动态内存分配

malloc

#include

向malloc申请的空间的大小是以字节为单位的(malloc是向内存申请要多少字节,malloc不知道你要的是int还是char之类的类型,它只认字节,系统只能把一定量的内存抛出,然后我们再根据所需要的数据类型来划分内存)

返回的结果是void*,需要类型转换为自己需要的类型

(int *)malloc(n*sizeof(int))

例如

int *a=(int *)malloc(n*sizeof(int)); //malloc向内存申请要多少字节,此时得到的是void*,所以我们将其强制转换为int *类型后再赋给int *a

这时我们可以通过malloc做动态内存分配来实现数组的大小是可变的,实际上也不叫可变,是运行时候才确定的数组大小

int number;

int *a;

int i; //c99之前规定所有变量必须在最上方定义

printf("输入数量:")

scanf("%d",&number);

a=(int *)malloc(number*sizeof(int)); //这时你把指针a当作数组就可以了

代码写到这其实也有问题,我们malloc向系统要了一块空间,用完了得还回去,使用free()

int number;

int *a;

int i; //c99之前规定所有变量必须在最上方定义

printf("输入数量:")

scanf("%d",&number);

a=(int *)malloc(number*sizeof(int)); //这时你把指针a当作数组就可以了

free(a);

没空间了?

如果申请失败则返回0,或者叫做NULL

你的系统能给你多大的空间?我们可以写个代码试试

#include

#include

int main(){

void *p;

int cnt=0;

while( ( p=malloc(100*1024*1024) ) ){ //括号内是100MB的意思

cnt++;

}

printf("分配到%d00MB的空间",cnt);

return 0;

}

free()

把申请得来的空间还给“系统”

申请过的空间,最终都应该要还

只能还申请来的空间的首地址

例如

int *p;

p=malloc(100);

p++;

free(p); //代码会崩溃,devc++好像不会,哈哈

那我们如果不小心free(NULL)行不行呢

int *p;

p=malloc(100);

p++;

free(NULL); //编译能通过

这故事是这样的,反正0不可能是malloc得到的地址,所有我们在free()中会判断如果给的是0,那它就不做事情,不会崩溃,可是有什么必要做这种事呢

因为我们其实应该有一种良好的习惯,一个指针定义出来后先初始化为0

void *p=0;

在这个之后,如果我们没有malloc或者由于某些原因malloc没有成功,这时我们free§,代码是不会崩溃的。

如果没有free(NULL)不会崩溃的机制,我们每次释放内存还得加上一个判断条件,很麻烦

if(p!=NULL){

free(p); //每次得多写这一串代码来防止代码崩溃

}

所以为了配合这种好习惯——在指针变量定义的时候,赋给指针一个0值,free(NULL)不会让代码崩溃

int *p=NULL;

p=malloc(100);

free(p); //无论malloc是否成功,都可以安全释放

常见问题

申请了没free————>长时间运行内存逐渐下降

新手:忘了

老手:找不到合适的free的时机

free过了再free

地址变过了,直接去free

解决办法是,一旦有malloc,就对上一个free

int sum(const int a[],int length);

指针运算

1+1=2,但我们如果对指针+1会发生什么呢?

我们可以分别用char和int指针来做个实验

char ac[]={0,1,2,3,4,5,6,7,8,9,};

char *p=ac;

printf("p =%p\n",p);

printf("p+1=%p\n",p+1);

int ai[]={0,1,2,3,4,5,6,7,8,9,};

int *q=ai;

printf("q =%p\n",q);

printf("q+1=%p\n",q+1);

p =0061febe

p+1=0061febf //p到p+1,确实是只加了1

q =0061fe94

q+1=0061fe98 //而q到q+1加了4

这是因为,p是char类型,q是int类型,而sizeof(char)=1,sizeof(int)=4

所以我们对指针加1,加的是指针对应类型的一个单元的长度,即sizeof

我们前面在讲到数组和指针的关系时,我们提到过当把一个数组赋给一个指针后,可以拿那个指针做像数组一样的操作,例

*p=ac[0];

*(p+1)=ac[1]; //这里加括号是因为:*是单目运算符,优先级比+高

*(p+n)<====>ac[n];

此时给指针加1表示要让指针指向下一个变量

如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

我们已经知道指针是可以加一个整数的,除此之外我们还可以

给指针加、减一个整数(+,+=,-,-=)

递增递减(++/–)

两个指针相减(此时结果不是两个地址的差,而是两个地址之间隔了多少个sizeof)

*p++

取出p所指的那个数据来,完事之后顺便把p移到下一个位置去

*的优先级虽然高,但是没有++高,所以我们不用加括号

常用于数组类的连续空间操作

在某些CPU上,这可以直接被翻译成一条汇编指令(所以这也是为什么C语言早期要保留+±-这种运算符的原因,因为可以直接转换成汇编指令,运行效率高)

我们通过数组遍历这件事来试试

正常情况下我们会这样写

int a[]={0,1,2,3,4,5,6,7,8,9,};

for(int i=0;i

printf("%d ",a[i]);

}

当我们使用*p++,可以把代码改成这样

int a[]={0,1,2,3,4,5,6,7,8,9,-1}; //这里加的-1是判断条件

int *p=a;

for( ;p!=-1;){

printf("%d ",*p++);

}

当然如果for循环是这样,我们可以改成while

int a[]={0,1,2,3,4,5,6,7,8,9,-1};

int *p=a;

while(p!=-1){

printf("%d ",*p++);

}

我们在学到字符串时,会大量看到这样的运算,到时候我们再仔细探究*p++到底有什么意思

指针比较

< <= == > >== !=都可以对指针做

比较它们在内存中的地址(地址大小)

数组中的单元的地址肯定是线性递增的

(指针没有乘除,没有意义)

0地址

现代的操作系统,无论是Windows、Marcos、Linux还是其他的Unix,这些操作系统都叫做多进程的操作系统,它的基本管理单元叫做进程

每一个进程运行起来后,操作系统会给它一个虚拟的地址空间,也就是说所有的进程在运行起来时都以为自己具有从0开始的一片连续的空间,任何一个进程里面都有0地址(虚拟的,具体是什么得看操作系统那门课)

内存中当然有0地址,但是0地址通常是个不能随便碰的地址

所以指针不应该具有0值

因此可以用0地址来表示特殊的事情

例如我们可以在指针变量定义时先赋给它一个0,如果我们后面没有对指针变量进行真正的初始化,0地址的指针就会让代码崩溃。好处是我们可以一眼查出来指针没有进行真正的初始化。

NULL是一个预定义的符号,表示0地址,我们建议以后要用0地址的时候用NULL来表示,因为有的编译器不愿意你用0来表示0地址

指针的类型

无论指向什么类型,所有的指针的大小都是一样的,因为都是地址(这里指的是指针所占的字节大小,而非地址大小)

但是指向不同类型的指针是不能互相赋值的,这是为了避免用错指针。当然如果你想,你可以做强制类型转换(作为初学者,我劝你不要轻易尝试这个事情),但我们后面讲这个是因为我们想带出另一个东西:void*

指针的类型转换

void* 表示不知道指向什么东西的指针(这个往往会用在某个底层系统程序里,我要直接去访问某个内存地址所代表的如外部设备、控制寄存器等)

计算时与char*相同(但不相通)

指针也可以转换类型

int *p=&i;

void *q=(void*)p; //把int的p强制转换成void*后赋值给q

但是这并没有改变p所指的变量的类型(i还是int),而是让后人用不同的眼光通过p看它所指的变量(也就是我们通过p或q去看i的时候,我不再当你是int,我就认为你是个void)

总结:用指针来做什么

需要传入较大的数据时用作参数

传入数组后对数组做操作

函数返回不止一个结果

用函数来修改不止一个变量

动态申请的内存

动态内存分配

输入数据时,如果先告诉你个数,然后再输入,要记录每个数据

C99可以用变量做数组定义的大小

int number;

scanf("%d",&number);

int a[number]; //C99

那C99之前呢,这时就必须使用动态内存分配

malloc

#include

向malloc申请的空间的大小是以字节为单位的(malloc是向内存申请要多少字节,malloc不知道你要的是int还是char之类的类型,它只认字节,系统只能把一定量的内存抛出,然后我们再根据所需要的数据类型来划分内存)

返回的结果是void*,需要类型转换为自己需要的类型

(int *)malloc(n*sizeof(int))

例如

int *a=(int *)malloc(n*sizeof(int)); //malloc向内存申请要多少字节,此时得到的是void*,所以我们将其强制转换为int *类型后再赋给int *a

这时我们可以通过malloc做动态内存分配来实现数组的大小是可变的,实际上也不叫可变,是运行时候才确定的数组大小

int number;

int *a;

int i; //c99之前规定所有变量必须在最上方定义

printf("输入数量:")

scanf("%d",&number);

a=(int *)malloc(number*sizeof(int)); //这时你把指针a当作数组就可以了

代码写到这其实也有问题,我们malloc向系统要了一块空间,用完了得还回去,使用free()

int number;

int *a;

int i; //c99之前规定所有变量必须在最上方定义

printf("输入数量:")

scanf("%d",&number);

a=(int *)malloc(number*sizeof(int)); //这时你把指针a当作数组就可以了

free(a);

没空间了?

如果申请失败则返回0,或者叫做NULL

你的系统能给你多大的空间?我们可以写个代码试试

#include

#include

int main(){

void *p;

int cnt=0;

while( ( p=malloc(100*1024*1024) ) ){ //括号内是100MB的意思

cnt++;

}

printf("分配到%d00MB的空间",cnt);

return 0;

}

free()

把申请得来的空间还给“系统”

申请过的空间,最终都应该要还

只能还申请来的空间的首地址

例如

int *p;

p=malloc(100);

p++;

free(p); //代码会崩溃,devc++好像不会,哈哈

那我们如果不小心free(NULL)行不行呢

int *p;

p=malloc(100);

p++;

free(NULL); //编译能通过

这故事是这样的,反正0不可能是malloc得到的地址,所有我们在free()中会判断如果给的是0,那它就不做事情,不会崩溃,可是有什么必要做这种事呢

因为我们其实应该有一种良好的习惯,一个指针定义出来后先初始化为0

void *p=0;

在这个之后,如果我们没有malloc或者由于某些原因malloc没有成功,这时我们free§,代码是不会崩溃的。

如果没有free(NULL)不会崩溃的机制,我们每次释放内存还得加上一个判断条件,很麻烦

if(p!=NULL){

free(p); //每次得多写这一串代码来防止代码崩溃

}

所以为了配合这种好习惯——在指针变量定义的时候,赋给指针一个0值,free(NULL)不会让代码崩溃

int *p=NULL;

p=malloc(100);

free(p); //无论malloc是否成功,都可以安全释放

常见问题

申请了没free————>长时间运行内存逐渐下降

新手:忘了

老手:找不到合适的free的时机

free过了再free

地址变过了,直接去free

解决办法是,一旦有malloc,就对上一个free