指针

指针:
指针的重要性:
表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是c的灵魂,也重要,也很难。努力掌握。
指针的含义:
指针就是地址,地址就是指针。
地址就是内存单元的编号,是从0开始的非负整数。
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
指针的本质就是一个操作受限的非负整数,它不能进行加,乘和除,只能进行相减
×××间接赋值(或者说间接修改变量的值)是指针存在的最大意义,而间接赋值就是用*加指针名来对它所指向的变量赋值
没有内存就没有指针,指针必须指向一块在程序中申请过的内存空间,这个指针才能使用,而不是野指针;
其实内存空间就相当于一个房间,每个房间都有门牌号,而指针就是房卡,房卡上写着门牌号, 你拿着房卡啥都干不了 必须打开门进去,才能对房间里的东西进行操作
二级指针就相当于一张房卡,这张房卡的房间里只有一个东西 那就是另一张房卡 你先拿着这张房卡找到那个房间取出那个房卡在根据那个房卡找到真正的房间在进行操作
比如
int i; //这就相当于申请了一个int类型房间,
int *p; //这就相当与得到了一张能打开int类型房间的房卡
p = &i; //这相当于把房间的门牌号写到房卡上
*p = 1; //相当于先打开门进去了 再把1放入房间
p--;// 这相当于你去了这个房间的下一个房间
两个指针只能减 减表示两个房间中间差多少房间,而且还必须是一栋楼 意思就是指向同一个连续的空间
要是你指向a数组 我指向b数组 我们两一减这是什么 都指向a数组一减才有意思
同样 两个指针相加、相乘和相除都没有意义
 
×××××××××××××××××××××××××××××××××××××××
 
int* p;//p是变量的名字,int*表示p变量存放的是int类型变量的地址
//int* p; 不表示定义了一个名字叫做*p的变量
//int* p; 应该这样理解:p是变量名,p变量的数据类型是int * 类型
// 所谓int * 类型 就是存放int变量地址的类型
int i=1;
 
p=&i;
/*
1.p保存了i的地址,因此p指向i;
2.p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不会影响p的值,
3.如果一个指针变量指向了某个普通变量,则
*指针变量 就完全等同于 普通变量
例子:
如果p是个指针变量,并且p存放了普通变量i的地址
则p指向了普通变量i
*p就完全等同于i
或者说: 在所有出现*p的地方都可以替换成i
在所有出现i的地方都可以替换成*p
*p 就是以p的内容为地址的变量
*/
 
基本指针常见错误解析
# include<stdio.h>
int main(void)
{
int i=5;
int* p;
int* q;
 
p=&i;
//*q=p; //error 语法编译会出错
//*q=*p; // error
p=q; //q是垃圾值,q赋给p,p也变成垃圾值
printf("%d ",*q);
/*
q的空间是属于本程序的,所以本程序可以读写q的内容,
但是如果q内部是垃圾值,则本程序不能读写*q的内容
因为此时*q所代表的内存单元的控制权限并没有分配给本程序
所以本程序运行到printf是就会出错
*/
 
return 0;
}
*********************************************************
 
* 的含义
1.乘法
2.定义指针变量
int* p;
//定义了一个名字叫p的变量,int*表示p只能存放int类型的地址
3.指针运算符(取地址符的逆运算;p=&i,*p等于i。前一句表示对指针变量p的定义,必须是有了定义的指针变量才能和*结合,后一句才生效。)
该运算符放在已经定义好的指针变量前面
如果p是一个已经定义好的指针变量
则*p表示 以p的内容为地址的变量
如:int i=1;
int* p;
p=&i;
假设i这个单元的地址为1000H,则 i的内容为1,p的内容为1000H,*p的内容就是i就是1。
 
********************************************************************************************************************************************************
通过被调函数修改主调函数(普通变量的值)
(1)实参必须为普通变量的地址
(2)形参必须为指针变量
(3)在被调函数中通过
*形参名=...
的形式就可以修改实参中的地址 从而达到交换变量的值
 
数组名就是表示数组中第一个元素的地址, a==&a[0]
a[i]等价于*(a+i) 同样&a[i]等价于(a+i)
(a+i)就是a数组中序号为i的元素的地址
*(a+i)就是该元素的值
语句p=a;等价于p=&a[0];
int a[5];// a是int*类型 它是地址
a=&a[3];//error 因为a是常量 不能位于等号左边
 
确定一个数组需要两个参数 一个是数组的开始的地方一个是数组的长度
就是需要两个形参 一个是地址(int*形式) 一个是数字(int形式)
 
这个函数是用来将一个数组反序输出的 比如本来是 1 2 3 4 5 变成 5 4 3 2 1
void inv(int* x,int n)
{
int * p,temp,* i,* j,m=(n-1)/2;
i=x;j=x+n-1;p=x+m; //将数组的第一个元素的地址赋给i最后一个元素的地址赋给j,
//因为数组是从0开始 所以要取中间元素的序号就得用(n-1)/2
for(;i<=p;i++,j--)
/*此处需注意所谓每循环一个的i++和j--的意思是i=i+1,j=j-1;
但是i和j表示的是地址 所以i+1,j-1实际上是i+1×d,j-1×d
d为一个数组元素所占的字节数
*/
 
{
temp=*i;*i=*j;*j=temp;
}
return;
}
 
××××××××××××××××××××××××××××××××××××
 
for(i=0;i<10;i++)
printf("%d",* p++);
//这里面的* p++的作用是先输出*P的值 再使p+1 这样下次循环时*p就是下一个元素的值
//*(p++)和*(++p)前者是先取*p的值在使p加1,后者是先使p加1再取*p的值,若p初值为a(既&a[0])则前者表示a[0]的值后者表示a[1]的值
++(*p)
表示p所指向的元素值加1 注意 元素值
比如:如果p=a,则++(*p)相当于++a[0],若a[0]的值为3,则在执行++(*p)后a[0]的值为4,是元素值加1而不是指针p的值加1
同样*(p--)和*(--p)是一样的道理
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子: sizeof(int) 它的值为4
sizeof(char) 它的值为1
sizeof(double) 它的值为8
sizeof(变量名)
功能:返回值就是该变量所占的字节数
 
一个指针变量 无论它指向的数据类型占几个字节,它自己本身只占四个字节
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
为什么一个指针变量本身32位系统占四个字节,64位系统占八个字节(能理解理解,不能理解跳过,下面我说的也可能有些地方不对)
 
总结我自身的理解:
cpu可以直接对内存条进行处理,硬盘中的数据要先调入内存条中cpu才能进行处理,cpu对内存条通过三条线
进行控制分别是控制线,数据线和地址线;控制线控制数据是只读还是只写是可读还是可写,数据线用于数据传输,
但数据传输的方向是通过控制线控制的,内存条中单元很多,具体对哪个单元进行操作是由地址线控制的。
 
前面说过,CPU能对内存控制,而CPU对内存的控制的三条线中的地址线是通过32根线来控制的,每一根线的通断电分别表示0和1
两种情况,就是说内存中每个单元的地址都是由32个0或者1来表示,比如某个地址用十进制表示出来是2 则这个地址在计算机存储
的情况就是前面31个0加上后面一个1 总共32个0或1 而一个0或者1的大小称为位(bit) 也就是说一个地址的所占的是32位,
规定8个位是一个字节,所以一个地址的所占的是32除以8等于4个字节
 
内存的大小:
内存中每个单元的地址都是由32个0或者1来表示,但它能表示的单元数就是2的32次方个单元,因为一根线有0和1两种情况,两根线
有四种情况,既2的平方,三根线有2的三次方个情况,那32根线是2的32次方个情况 也就是2的32次方个单元,(注意,在计算机中
字节是最小的单元 一个字节是八位,所以说,一个字节是一个地址,而不是一个位就是一个地址) 既2的32次方个B(B就是字节)
那么2^32(B)=2^22(KB)=2^12(MB)=2^2(GB) 32位的cpu只能支持4G的内存 同理那64位的理论上能支持4^32GB内存 大约是
1亿多G的内存,而现在主流主板最高支持16G内存,而现在的64位CPU也只是比32位高一点,并没有达到理论上的64位。
64位的cpu的指针占八个字节,32位cpu的指针占四个字节,就是地址线的数目来决定的,也就是内存的大小来控制,内存大了,存储空间多了
需要的门牌号也就多了
 
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
 
指针和二维数组
(有点难,理解了就挺简单的,下面的总结是根据我的理解来的,我这语言组织有点问题,水平到这了,以后再继续添加)
我在上面提到过,二维数组实质上就是每个元素都是一维数组的一维数组
你可以这样理解,有一个一维数组,它的每个元素都是一个一维数组,那么这个
一维数组的每个元素的内容就是一个一维数组,而每个元素的名就是它的内容的地址
 
比如说 有一个数组a[i]它的每一个元素里面都是有j个元素的一维数组那么它就是a[i][j]
而a[i]就是i行的地址0列的地址 //i行 是序数为i的行 而不是第i行应该是i+1行
而在指针中 a[i]就表示为*(a+i) 这就表示这个指针变量指向i行
a[i][j]的地址就是*(a+i)+j a[i][j]的值就是*(*(a+i)+j)
不要和一维数组组搞混 只要想着 本来在一维数组中表示数组元素的值的在二维数组中都是地址
如a[0]就是0行0列元素a[0][0]的地址 a和&a[0]则是0行的首地址 虽然他两数值一样但含义却不一样
 
例:
#include<stdio.h>
int main(void)
{
int a[3][4]={{1,2,3,4},{6,7,8,9,11},{12,13,14}}//意思就是有一个元素个数为三个的一维数组他们的内容为别是a[0]={1,2,3,4} a[1]={6,7,8,9} a[3]={11,12,13,14}
printf("%d,%d ",a,*a); //0行首地址和0行0列元素地址
pritnf("%d,%d ",a[0],*(a+0));//0行0列元素地址
printf("%d,%d ",&a[0],&a[0][0]);//0行首地址和0行0列元素地址
printf("%d,%d ",a[1],a+1);//1行0列元素地址和1行首地址
printf("%d,%d ",&a[1][0],*(a+1)+0);//1行0列元素地址
printf("%d,%d ",a[2],*(a+2));//2行0列元素地址
printf("%d,%d "&a[2],a+2);//2行首地址
pritnf("%d,%d ",a[1][0],*(*(a+1)+0));//1行0列的元素的值
pritnf("%d,%d ",*a[2],*(*(a+2)+0);//2行0列元素的值
 
return 0;
}
 
在程序中 定义一个指向二维数组指针的格式
int (* p)[4]; // 这就是定义了一个指针变量p来指向包含了四个元素的一维数组 此时p的类型就不是int *了 而是int(*)[4]了
// 意思是 p被定义成指向一维整形数组变量的指针,一位数组有4个元素 因此p的基类型是一维数组,其长度为16(4个元素,每个元素4个字节)
使p指向数组a时有两种情况
(1):
int a[3][4]={.....}; //定义了一个二维数组并使它初始话
int (* p)[4];
p=a; // 使p指向二维数组a的0行, 不能写成”p=&a“,这样写直接报错 不兼容指针的赋值
 
printf("%d",*(*(p+2)+2)); //表示 输出a[2][2]的值
(2):
int a[4]={1,2,3,4}; // 定义了一个一维数组并使它初始化
int (* p)[4];
p=&a; // 使p指向这个一维数组 不能写成“p=a;” 这样写表示p的值是&a[0],指向a[0] ”p=&a;“表示p指向了一维数组(行),(* p)[3]表示 此行中的序号为3的元素
 
printf("%d",(*p)[3]); //输出a[3],则输出的就是整数4
 
用指针变量作形参,已接受实参数组名传来地址,可以有两中方法:(两种定义变量的方式不同,接受时实参的形式也不同)
(1)用指向变量的指针变量
a[3][4]={{1,2,3,4},{6,7,8,9},{11,12,13,14}}
int a_p(int* p,int n); //声明函数a_p
a_p(*a,12); //调用函数a_p
(2)用指向一维数组的指针变量
a[3][4]={{1,2,3,4},{6,7,8,9},{11,12,13,14}}
int a_p(int(* p)[4],int n);//p是指向包含四个元素的一维数组
a_p(a,12)
×××××××××××××××××××××××××××××××××××××××××××××××××
 
通过指针引导字符串
(字符指针做函数参数)
程序的改写:
原函数:(使字符串from的内容复制到字符串to里)
void q(char * from,char * to)
{for(;*form!='';from++,to++)
{*to=*from;}
*to='';
/*每个字符串最后一个字符都是‘’ 系统会自动加上去 而复制的字符串to却不会被加上去
由于当字符串*from为0时后结束了for循环 所以必须手动再给to的最后一位加上‘’ */
}
 
改写1:
void q(char * from,char * to)
{while((*to=*from)!='')
{to++;from++}
/* 当*to=‘’时 停止循环 所以不用再手动添加to的最后一位
将赋值和判断全部放入whlie的语句括号内表达式中 先赋值后判断 */
}
 
改写2:
void q(char * from,char * to)
{while((*to++=*from++)!='');}
/*将增值和赋值语句合并, 先赋值 在增值*/
改写3:
void q(char * from,char * to)
{while(*from!='')
*to++=*from++;
*to='';
}
改写4:
void q(char * from,char * to)
{while(*from)
*to++=*from++;
*to='';
}
/* 字符可以用ASCII码来替代 而‘’的ASCII码为0 所以while(*from!='')可以用 while(*from!=0)*来代替
而若*from的值不等于0while(*from!=0)和while(*from)是等价的 */
改写5:
void q(char * from,char * to)
whlie(*to++=*from++);
whlie(*to++=*from++);与while((*to++=*from++)!='');等价
同时 whlie语句还可以改成for语句
for(;(*to++=*from++)!=0;);或for(;*to++=*from++;);
指针的基本类型
1.int a; 整形变量
2.int *a; 整形指针变量
3.int **a; 整形指针的指针变量(一个指向指针的指针,它指向的指针指向一个整形数)
4.int a[10]; 整形数组
5.int *a[10]; 整形指针数组(一个数组中有10个指针,该指针指向一个整形数)
6.int (*a)[10]; 整形数组指针(一个指向有10个整形数组的指针) //就是指向一个二维数组
7.int (*a)(int); 函数指针变量(一个指向函数的指针,该函数有一个整形参数并返回一个整形数)
8.int (*a[10])(int); 函数指针数组(一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整形数组
做了几道题才感觉指针是真的难 自己慢慢学吧
自从学了接口封装 又感觉难了
************************************************
 
继续指针补充, 指向常量的指针和常量指针,指针函数和函数指针,至于指针数组和数组指针往上看几行就有了。
 
一、(1)常量指针
指针常量就是有一个常量它是一个指针;意思就是这个指针是一个常量,不能改变它的值,就比如加1减1之类的,意思就是他只能指向一个地方
不能让他指向别的地方。
定义方法:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int * const p = a; //这个指针指向整型数组a的开始
p++; //不允许,因为它只能指向a的开始
p = &a[2] //不允许
*p = 6; //没问题, 更改a[0]的值
(2)指向常量的指针(有的地方叫指针常量,但是在C++Primer书中叫的是指向常量的指针,按书上来,容易理解)
这个的意思就是有一个指针,它指向了一个常量;这个意思是这个指针它指向的数据无法改变,那就不能通过指针来改变了。
定义方法:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
const int * p = a; //p指向整形数组a的开始
p++; //没问题 让p指向a[1]
*p = 6; //不允许, 不能通过常量指针来更改原数组的值
(3)指向常量的常量指针
这个意思就是与一个指针它是常量同时它指向的数据也是一个常量,意思是既不能让这个指针指向别处,又不能通过它来改变它指向的数据的值
定义方法:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
const int * const p = a;
看一个指针是常量指针还是指向常量的指针看const 的位置 看const限定的是这个指针的名字还是这个指针的类型
二、(1)指针函数
这个就是有一个函数,它的返回值是一个指针,称为返回指针的函数,即指针函数
int *f(int i, int j);
(2)函数指针
这个就是有一个指针,它指向一个函数
int (*f)(int i,int j);
声明一个函数指针它必须和它要指向的函数的样式是一样的,比如他要指向的函数有两个int的形参,返回值是int数据,那么这个指针的声明形式必须一样
最简单的方法就是先把要指向的函数原型写出来 然后把函数名换成(*指针名)的形式,注意圆括号不能少。
这两个的区别就是f先和谁结合 ()的优先级高于*
所以要看f和*是不是括在一起 没有括在一起就说明f先和后面的结合那f就是函数
括在一起 就说明f先和*结合 那f就是指针。
×××××××××××××××××××××××××××××××××××
malloc函数
(动态内存分配,malloc是memory(内存)allocate(分配)的缩写)
(从书上搬过来的)
#include<stdio.h>
#include<malloc.h> //不能省
int main(void)
{
int i=5;//分配了4个字节,静态分配
int * p=(int*)malloc(4);
/*
1.要使用malloc函数,必须添加malloc.h这个头文件;
2.malloc函数只有一个形参,并且这个形参为整数
3.4表示请求系统为本程序分配4个字节
4.malloc函数只能返回第一个字节的地址
5.本行分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
6.p本身所占内存是静态分配的,p所指向的内存是动态分配的
*/
*p=5; //*p代表的就是一个整型变量,只不过这个*p整型变量的内存分配是动态分配的,和上面的整型变量i不一样。
free(p); //free(p)表示把p所指向的内存给释放掉,p本身内存是静态内存,不能由程序释放只能在本函数结束后由系统释放
 
return 0;
}
 
上面程序中 p 是int*类型 则&p是int**类型
int i; //整型变量
int* p; //p存放的是int类型变量的地址 即可以存放i的地址
int** q; //q存放的是int*类型变量的地址 所以只能存放p的地址而不能存放i的地址
int*** r; //同样 r存放的是int**类型的地址 故只能存放q的地址而不能存放p和i的地址
 
p=&i;*p=i;**q=i;***r=i
 
动态内存分配(书上抄的,方便以后用的时候翻出来看)
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,realloc,free这四个函数
1 使用 malloc
函数原型为
void * malloc(unsigned int size); //unsigned int 意思为形参size的类型定为无符号的整数类型(不允许为负数)
其作用是在内存的动态存储区中分配一个长度为size的连续空间,此函数的值(即“返回值”)是所分配的第一个字节的地址,或者说,
此函数是一个指针型函数,返回值指向该分配域的开头位置;
如:
malloc(100); //开辟100字节的临时分配域,函数值为其第一个字节的地址
注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。
2 使用calloc函数
函数原型为
void * calloc(unsigned n,unsigned size);
作用为在内存的动态存储空间中分配n个长度为size的连续空间,一般比较大,足以保存一个数组
calloc函数可以为一维数组开辟动态存储空间,n为数组元素的个数,每个元素长为size,这就是动态数组
函数返回指向所分配域的起始位置的指针,分配不成功,返回NULL
如:
p=calloc(50,4); //开辟50x4个字节的临时分配域,把起始地址赋给指针变量p
3 使用free函数
函数原型
void free(void * p);
作用是释放指针变量p所指向的动态空间,p应是最近一次调用calloc或malloc函数时得到的函数返回值
如:
free(p); //释放指针变量p所指向的已分配的动态空间
free函数无返回值。
4 使用realloc函数
函数原型为
void * realloc(void * p,unsigned int size);
作用是已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配
realloc(p,50); //将p所指向的已分配的动态空间改为50
以上四个函数的声明在stdlib.h头文件中,在用到这些函数时应当用#include<stdlib.h>指令把stdlib.h头文件包含到程序文件中。
 原创,转载标明出处,谢谢!!

原文地址:https://www.cnblogs.com/wangweigang/p/8990237.html