您的位置:首页 > 编程语言 > C语言/C++

C语言学习笔记(五)——指针【C语言的灵魂】

2015-06-22 12:12 447 查看


第五章 指针【C语言的灵魂】



指针【C语言的灵魂】

实质:

指针的实质就是地址,地址就是指针

地址就是内存单元的编号

指针变量是存放地址的变量

指针和指针变量是两个不同的概念

但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们的含义并不一样。

学习指针后,应将变量空间看作两部分内容:变量的内容和变量的地址。

实质:不在同一函数的两个指针,都可以指向同一变量。

注意:&取地址符不能取常量和表达式的地址。

指针的重要性:

1.表示一些复杂的数据结构

2.快速的传递数据,减少内存耗用

使用指针指向主函数中的变量,从而直接修改主函数中的变量,且只发送4个字节的地址,速 度快,效率高。

3.使函数返回一个以上的值

4.能直接访问硬件

5.能够方便地处理字符串

6.是理解面向对象语言中引用的基础

指针的分类:

1.基本类型指针

详见经典指针程序_互换两个数.cpp

2.指针和数组

3.指针和函数

4.指针和结构体

5.多级指针



指针与函数

函数的形参与实参永远不可能是同一个变量,通过指针指向实参可以使
*形参与实参为同一变量,在


函数内部将实参的值改变。

【指针:改写主函数中变量的值】

要想改变主函数中变量的值,实参必须发送地址。

通过传值,形参永远不可能改变实参的值,只能返回一个值,但可以通过指针,发送(复制)地址给形参,从而改变实参的值(即返回多个值)。



指针与数组(地址的运算)

一维数组名

是个指针常量,存放的是数组中第一个元素的地址,固定指向数组中第一个元素,不可被赋值
改变。



inta[5]; //a是int
*类型

*a= 9; //一维数组名即表示数组中第一个元素的地址,此处元素a[0]被赋值9。

*(a+1)= 10; //依次类推,a+1、a+2...a+i分别表示第2个、第3个...第i+1个元素。

地址+1即为数组中下一连续元素的地址。

下标与指针

如果p是个指针变量(或数组名),则
p[i] 永远等价于 *(p+i)。

指针变量加下标即表示数组元素,正如a[i]表示数组元素一样,a本身就是指针变量。

函数对数组进行处理,需要接收两个参数:函数第一个元素的地址(即数组名)和数组元素的个数。

即确定一个数组需要两个参数(首地址与长度),只要知道函数第一个元素的地址(即数组名)和数组 元素的个数,那么数组中所有元素的信息都可以知道。



指针的运算

指针变量不能相加,不能相乘,也不能相除。

如果两个指针变量指向的是同一块连续空间中的不同存储单元,

则这两个指针变量才可以相减。

正如,在同一小区的门牌号才可以相减,不在同一小区的门牌号相减无意义。

指针的相减运算就是十六进制数的相减运算。

inti = 3;

intj = 5;

inta[50];

int* p = &i;

int* q = &j;



q- p; //不在同一存储空间的两个变量i、j的地址相减无意义。



p= &a[1];

q= &a[44];



printf("%d\n",q-p); //即为十六进制数的相减,然后以十进制输出。

动态内存分配

内存分配头文件:<malloc.h>

malloc.h文件包括malloc()函数、free()函数、realloc()函数等操作内存函数。

在使用时应注意声明<malloc.h>头件。

malloc是memory(内存)allocate(分配)的缩写。

inti = 3; //静态分配内存,给变量i分配了4个字节空间。

int* p = (int *)malloc(20);

/*

1.动态分配内存,要使用malloc函数,必须添加malloc.h头文件
(函数声明)。malloc是
memory(内存) allocate(分配)的缩写。

2.malloc函数只有一个形参,并且形参是整型。

3.20表示请求系统为本程序分配20个字节内存空间。

4.malloc函数的返回值为第一个字节的内存地址,赋值给指针变


量p。


malloc函数的返回值为void *
类型,void *类型也叫干地址


,即无实际意义的地址。


并且需要强制类型转换(int *),将所分配的内存转换为指针变


量p所定义的int
* 类型。


共分配了20个字节,每个int类型变量占4个字节,所以共生成

了5个变量(动态数组)。


第一个变量为*p,第二个为*(p+1),依次类推,第i个为*(p+i)
5.
注意:指针变量p所占的内存是4个字节,为静态分配的,p所指

向的内存为动态分配的。

6.如果内存分配失败,则malloc函数会返回地址NULL,所以经常在调用
malloc函数后写上if (NULL == p);语句来判断是否分配成功。

*/



*p= 3; //为第一个变量赋值3

*(p+1)= 5; //为第二个变量赋值5



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



free(p);//free()函数,将p指向的内存空间释放掉。注意:p本身的内存空间是静态的,不
会被释放,只能在程序结束后被系统释放。

动态构造一维数组:

inta[5]; //静态构造了5个元素的一维数组。

int* pArr;

intlen;

inti;



printf("请输入需要存放的元素的个数:");

scanf("%d",&len);

pArr= (int *)malloc(4*len);


/*

动态构造了一维数组,长度为len,数组类型为

int类型,数组名即为pArr。类似于
int a[5]; 数组名PArr与a相同。


动态数组的操作与静态数组相同,数组名pArr存


放了数组第一个元素的地址。

*/

for
(i=0;i<len; ++i)

{

printf("请输入第%d个元素的值:",
i);

scanf("%d",&pArr[i]); //与静态数组操作相同。

}



printf("一维数组中的内容为:\n");

for(i=0; i<len; ++i)

printf("a[%d]= %d\n", i, pArr[i]);



realloc(pArr,100); /*

realloc()重新分配内存函数,用于改变数组的长度,后跟两个参数, 第一个是数组名,第二个是重新分配的数组字节数,


如此函数即将pArr数组所占内存改为100字节。


当内存改变时,增长:如50字节变为100字节,前50字
节内容保留不变,再增加50字节内存。


缩小:如150字节变为100字节,前100字节内容保留,


后50字节内容丢失。

*/

free(pArr); //free()函数释放指针变量所指向的变量内存,当释放数组名时,将全

//部释放整个数组所占的内存,而不是只释放数组名所指向的第一个元

//素变量内存。



printf("一维数组中的内容为:\n");

for(i=0; i<len; ++i)

printf("a[%d]= %d\n", i, pArr[i]);



动态内存与静态内存比较:

静态内存是由系统自动分配,由系统自动释放。

静态内存是在栈中分配的。



动态内存是由程序员手动分配,手动释放。

动态内存是在堆中分配的。



内存分为五大区,其中在栈中分配的内存调用时压栈,完毕后出栈,内存被释放,不能够跨函
数使用,而在堆中分配的内存不存在这种情况,可以跨函数使用。



多级指针

多级指针,即为指针的指针,取指针变量的地址。

inti = 10;

int* p = &i;

int** q = &p; //p为int
*类型,&p即为int
** 类型。

int*** r = &q;



//p= &r; //错误,r为int
*** 类型,&r即为int
**** 类型,而p为int
* 类型,类型不一
//致,无法赋值。

printf("i= %d\n", i);

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

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

printf("i= %d\n", ***r); //*r是q,**r是p,等价于*q,***r是i,等价于*p。

int** p如何理解:p是int
** 类型,*p就是int
* 类型,**p就是int类型。(星号的移动)

注意:要在另一函数中修改谁,就应该发送谁的地址,以其指向(包含多次指向)来修改,而 不可能修改发送的变量内容。

如:
int * p;


change(p); //错误,要修改p的指向,应该发送p的地址,而不能发送p本身。

即:
int * p;


change(&p); //以*q == p,*q指向来实现修改p的指向。



跨函数使用内存

上已介绍,静态内存是在栈中分配的,函数运行结束后内存被释放,不能跨函数使用。

动态内存是在堆中分配的,函数运行结束内存空间仍然存在,所以可以跨函数使用。

学习指针的重要用途就是为了跨函数使用动态内存变量,在跨函数使用中,传递的都是地址。





指针变量所占字节

sizeof()函数:

sizeof(数据类型或变量名)

sizeof()函数返回值为该数据类型或该变量所占字节数。

如:sizeof(int)
= 4; sizeof(double) =8; sizeof(char) = 1;


sizeof(int * ) = 4; sizeof(double * ) = 4;sizeof(char * ) = 4;

结论:一个指针变量无论是什么类型指针变量,指针变量都占4个字节。

原因:

在内存中,8位二进制数为一个字节,一个字节为一个基本单元,有一个编号。

如:double类型数据,占8个字节,所有有8个编号,但C语言中规定,一个变量地址

的编号以其第一个编号作为其编号,所以无论是什么类型数据,其编号都只有一个,

一个十六进制地址编号占4个字节,所以指针变量也就占4个字节。


拓展:

32位CPU有32根地址总线,2^32=4G,所以32位CPU最大支持4G内存。

每个地址编号都是由32位二进制数组成,所以占4个字节,转换为十六进制表示。

地址总线进行寻址是由32根地址总线确定的。



//本函数功能是输出任意一维数组。

voidstring(int * p, int l) //函数对数组进行处理,需要接收两个参数:函数第一个元

{
素的地址(即数组名)和数组元素的个数。

inti;



for
(i=0;i<l; ++i)

printf("a[%d]= %d\n",i, *(p+i));

}

【空指针NULL】

NULL代表的是内存单元的地址编号00000...0

计算机中规定了以0为编号的内存存储单元的内容不可读,不可写。

操作系统中很重要的数据会放在以0为编号的内存存储单元中。



int* p; p
= NULL; //就不可再对*p中的内容进行操作。

经常这样写:free(p); p
= NULL; //p所指向的变量空间已被释放,不能再读写,为防止程序出错崩溃,写

上p
= NULL;防止再对*p进行操作。

注意:空指针NULL是四个大写字母,不能写成小写。



指针常见问题



1.若普通变量未被初始化,则会存放垃圾值;


但若指针未被初始化,则会出错,因为指针变量存放了未知的地址,指向了未知的变量,


指针变量可使用,即地址可使用,但 *指针变量的值不属性本程序,无权读取或访问。




2.普通变量不同类型之间可以相互赋值,虽然可能会有精度丢失;


但指针变量不同类型之间即使不会丢失精度也不能够相互赋值,如:long * p;即定义p只能存放

long类型变量地址,不能存放double类型变量地址,也不能存放int类型变量地址,因为指针变量
只保存了变量第一个字节的地址,即只指向第一个字节,但int * p规定了p指向的变量占4个字节,
以此来确定变量。




3. &:取地址符,即取变量的地址,不仅可以取普通变量的地址,也可以取指针变量的地址,即指针
变量也是变量,只要分配了变量空间,就可以取其地址。



星号*
的三种含义:


1.乘号

2.定义指针变量

3.指针运算符(为取地址符
&的逆运算)



查看变量的地址

把变量的地址以十六进制输出(地址编号都是以十六进制表示的)

%x(X):输出为十六进制整型(字母大小写) %#x(X):输出为Ox(X)十六进制整型

printf("%#X",&i); //以十六进制整型输出变量i的地址。

或用%p以32位地址十六进制控制输出。





例1.


注意:指针变量p为int
* 类型,*p为int类型,即p存放了整形变量的地址,*p代表整形变量。(*与&为互逆运算)

int* p; //定义整形指针变量,p是指针变量的名字,int
* 表示p变量存放的是int类型变量的地址。


//p变量为int *
类型,即存放int变量地址的类型。

inti = 3;



p= &i;

/*

1. p存放了i的地址,因此p指向i,通过p可以找到i。

2. p不是i,i也不是p,p与i是两个不同的变量。更准备地说:修改p的值不影响i的值,修改i的值也不
影响p的值。


3.
如果一个指针变量指向了某个普通变量,则


*指针变量 就完全等同于 普通变量

例:如果p是个指针变量,并且p存放了普通变量i的地址

则p指向普通变量i

*p 就完全等同于
i, *p == i
(*是取地址&的逆运算,可认为是取出指针所指向的普通变 量。)


或者说:在所有出现*p的地方都可以替换成i(除定义声明外)


在所有出现i的地方都可以替换成*p


*p表示以p的内容为地址的变量

所以,*p就是
i,修改 *p
的值就是修改i的值。


*/



printf("i= %d\n", i);

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



*p= 99; //即修改了i的值。



printf("i= %d\n", i);

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



例2.

inta[5] = {1,2,3,4,5};

inti;



for(i=0;i<5; ++i)

printf("a[%d]= %d\n", i, *(a+i)); //此处写*(a+i)和a[i]都是完全等价的。



例3.

voidarr(int * p, int len)

{

inti;



for(i=0; i<len; ++i)

printf("a[%d]= %d\n", i, p[i]);
//此处写*(p+i)和p[i]都是完全等价的。


//*(p+i)、p[i]、a[i]和*(a+i)都是同一个变量。


return;
//
【p[i] == *p(p+i) ==a[i] == *(a+i)】

}



intmain(void)

{

inta[10] = {1,2,3,4,5};

arr(a,10);

return0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: