指针是分类型的:
指针++根据类型不同,偏移值也不同。指针和数组,如何让指针指向数组?
①用数组名 :int array[10],int* p,p=array
②用第一个元素的地址:int array[10],int* p,p=&array[0]
注意:
不要让指针的偏移位置超出了数组,否则将看到乱码。
#include <stdio.h>
#include <stdlib.h>
void arrayAdrePrint(int datas[],int size)
{
int i;
for(i=0;i<size;i++){
printf("第%d个元素的地址是:%p\n",i+1,&datas[i]);
}
}
void arrayPrint1(int datas[],int size)
{
int i;
for(i=0;i<size;i++){
printf("arrayPrint1打印第%d个元素的地址是:%p\n",i+1,datas);
datas++;
}
}
void arrayPrint2(int* p,int size)
{
int i;
for(i=0;i<size;i++){
printf("arrayPrint2打印第%d个元素的地址是:%p\n",i+1,p);
p++;
}
}
int main()
{
int *p1;//整型类型只能存放整数的地址
char *p2;//字符型只能存放字符数据的地址
int a=10;
char c='A';
p1=&a;
p2=&c;
//指针++
printf("a的地址打印:%p\n",p1);
printf("a的地址++打印:%p\n",++p1);
printf("c的地址打印:%p\n",p2);
printf("c的地址++打印:%p\n",++p2);
//结果是:a的地址++后,向后偏移了四个字节,c的地址++后向后偏移了1个字节
//原因是:整型占4个字节,字符型占一个字节
int len;
int array[3]={1,2,3};
len=sizeof(array)/sizeof(array[0]);
arrayAdrePrint(array,len);//打印数组的地址,由程序结果可知数组元素的地址是连续的。
int* parray;
parray=array;
arrayPrint1(array,len);
arrayPrint2(parray,len);//通过指针++的方式访问数组
system("pause");
return 0;
}
指针数组函数的综合:
#include <stdio.h>
#include <stdlib.h>
void initScores(int *datas,int size)
{
int i;
for(i=0;i<size;i++){
printf("请输入第%d个学生的分数\n",i+1);
scanf("%d",datas);
if(*datas<0 || *datas>100){
printf("输入错误,强制退出\n");
system("pause");
exit(-1);
}
datas++;
}
}
void arrayPrint(int *datas,int size)
{
int i;
for(i=0;i<size;i++){
printf("第%d个学生的成绩是%d\n",i+1,*datas++);
}
}
int findMax(int *datas,int size)
{
int i;
int max=0;
for(i=0;i<size;i++){
if(*datas>max){
max=*datas;
}
datas++;
}
return max;
}
int findMin(int *datas,int size)
{
int i;
int min=*datas;
for(i=0;i<size;i++){
if(*datas<=min){
min=*datas;
}
*datas++;
}
return min;
}
float findAverge(int *datas,int size)
{
int i;
int sum=0;
float ave;
for(i=0;i<size;i++){
sum=*datas+sum;
datas++;
}
ave=(float)sum/size;
return ave;
}
void printRet(int data1,int data2,float data3)
{
printf("最高分是:%d,最低分是:%d,平均分是:%f\n",data1,data2,data3);
}
int main()
{
int scores[3];
int len;
int max;
int min;
float averge;
len=sizeof(scores)/sizeof(scores[0]);
//1、初始化数组
initScores(scores,len);//函数传参的过程实际上就是赋值的过程
//1.1打印数组
arrayPrint(scores,len);
//2、找到最高分
max=findMax(scores,len);
//3、找到最低分
min=findMin(scores,len);
//4、算出平均分
averge=findAverge(scores,len);
//5、输出结果
printRet(max,min,averge);
system("pause");
return 0;
}
为什么要用指针:
①可以将数值存放在固定的地址中
#include <stdio.h>
#include <stdlib.h>
//0060FEF8这个是我的电脑a的地址
//能不能让a也就是10,强制的保存在我要的地址 比如将a放在0060FEF4地址是十六进制的数
int main()
{
int a;//a的地址肯定是系统随机分配
a=10;
int *p;
p=&a;
printf("a's address is %p\n",p);
//(int *)0060FEF4表示将这个整型数强制转化为一个整型数的地址
int *p2=(int*)0x0060FEF4;//这种写法在以后arm架构,裸机编程·,arm驱动可能用到
*p2=10;
printf("在内存的%p位置,存放值是%d\n",p2,*p2);
volatile int *p2=(volatile int*)0x0060FEF4;
//volatile是类型修饰符
/*寄存器比内存更靠近CPU,所以数据在内存中访问的更快,当前我们写的
都是单线程的程序从main函数进来,我们定义一个变量是存放在内存中的,
这时候我们对变量的访问是将变量拷贝一份到寄存器,下次cpu访问的时候
直接从寄存器访问,这样效率更高,若改变变量的值,它会自动的将变量的
值更新到寄存器。若是多线程编程,同样是变量存放在内存中,两个线程都可以
对变量进行修改(除非对内存做了一些限定),若线程二改变变量的值,他不会
通知线程一将变量的值更新到寄存器,这样CPU用的只就不是最新的值,程序的
结果就可能会有问题。而volatile的作用是让CPU直接访问内存,不通过寄存器,
这样提高了正确性,但是降低了效率*/
system("pause");
return 0;
}
volatile和编译器的优化有关:
编译器的优化:
在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时会把变量读取到一个寄存器中,以后再读取变量值时,就直接从寄存器中读出;当变量在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。
当变量因别的线程值发生改变,上面寄存器的值不会相应的改变,从而造成应用程序读取的值和实际的变量值不一致。
当寄存器因别的线程改变了值,原变量的值也不会改变,也会造成应用程序读取的值和实际的变量值不一样。
volatile详解:博客
②可以通过地址改变变量的值
在这里插入代码片#include <stdio.h>
#include <stdlib.h>
/*void swap(int a,int b)
{
int tmp;
tmp=a;
a=b;
b=tmp;
}这个函数不能交换a和b的值*/
//指针变量本身也有地址,指针变量的值是其他变量的地址
void swap(int *a,int *b)
{
int tmp;
tmp=*a;
*a=*b;
*b=tmp;
}
int main()
{
int a=5;
int b=10;
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
system("pause");
return 0;
}
指针数组(好多个指针放在一个数组里):
#include <stdio.h>
#include <stdlib.h>
void int1Print(int** array)//因为实参本身就是一级指针
//所以要想让数组的首地址作为参数就要用二级指针
{
int i;
for(i=0;i<3;i++){
printf("指针偏移方法:第%d个元素是;%d\n",i+1,**array);//array本身是一级指针
//加一个*代表是取指针数组中所存放的地址
//加二个*代表取第一个元素地址所存放的值
*array++;//表示将指针后移指向数组中下一个地址
}
}
void int2Print(int** array)//array是一级指针数组的首地址,它的地址是二级指针
{
int i;
for(i=0;i<3;i++){
printf("数组元素++方法:第%d个元素是;%d\n",i+1,*array[i]);
}
}
void address1Print(int** array)
{
int i;
for(i=0;i<3;i++){
printf("第%d个元素是:%p\n",i+1,*array);
array++;
}
}
void address2Print(int** array)
{
int i;
for(i=0;i<3;i++){
printf("第%d个元素是:%p\n",i+1,array[i]);
}
}
int main()
{
int a=1;
int b=2;
int c=3;
int i;
int array[3];//多个整数组成的数组叫做整数数组
int* p;//整形指针变量
//定义指针数组格式:类型名* 数组名[]
int* parray[3];//指针数组就是多个指针组成的数组,数组中元素是指针变量
//指针变量是存放地址的变量
parray[0]=&a;
parray[1]=&b;
parray[2]=&c;//三个普通没有任何关系的整型变量的地址存入数组
int1Print(parray);//通过指针打印数组中地址所存放的数据
int2Print(parray);
address1Print(parray);//输出指针数组中的地址
address2Print(parray);
system("pause");
return 0;
}
数组指针(数组的指针):
#include <stdio.h>
#include <stdlib.h>
//上节课说的是“指针数组”,是一个数组里有多个指针
//本次课说的是“数组指针”,一个指针
//不管是何种类型的指针变量,都是存放别人地址的变量
void addressPrint(int* data)
{
printf("函数内输出array2++的地址:%p\n",++data);
}
int main()
{
int i;
int (*array1)[3];//数组指针的定义,整型数组指针!=整型指针,因为不是一个类型的指针
//比如:int* p,int (*array)[],在linux环境下不能p=array
//这个数组指针指向的是整个数组,若array1++,偏移的是整个数组所占的字节=3*4=12.
//定义格式:数组的类型 (*数组的名字)[]
int array2[3]={1,2,3};
int* p;//此指针并非是数组指针
//仅仅是一个普通的整形指针,刚好指向了数组的首元素地址array
array1=array2;
p=array2;
printf("元素名称打印数组2的地址是:%p\n",array2);
printf("首元素地址打印数组2的地址是:%p\n",&array2[0]);
printf("用数组指针赋值打印数组2的地址是:%p\n",array1);
printf("用整型指针赋值打印数组2的地址是:%p\n",p);//这几种方式输出的地址是一样的0060FEDC
printf("=================区别如下================\n");
//printf("array2++的结果是:%p\n",++p);
// printf("array2++的结果是:%p\n",++array2);因为array2是数组名是常量,不能用来++输出地址,可以将它赋给指针变量++输出如:printf("array2++的结果是:%p\n",++p);
//但是如果将数组名作为参数传入函数,则可以用++来输出整个数组的偏移值。如函数addressPrint()
addressPrint(array2);//输出结果:0060FEE0 偏移值:0060FEE0-0060FEDC=4,是一个数组内元素的大小
printf("array1++的结果是:%p\n",++array1);//输出结果:0060FEE8 偏移值:0060FEE8-0060FEDC=12,正好是整个数组的大小
system("pause");
return 0;
}
函数指针:
#include <stdio.h>
#include <stdlib.h>
//函数指针,顾名思义它是存放一个函数的地址
//如何定义?如何通过指针调用该函数?
void printfWelcome()
{
printf("欢迎来到啊哈C\n");
}
int add(int a,int b)
{
return a+b;
}
int main()
{
int i=10;
printf("变量名:i=%d\n",i);//通过变量名来访问一个变量
int* p=&i;
printf("指针:i=%d\n",*p);//通过指针访问变量
printfWelcome();//通过函数名来调用函数
//如何定义函数指针
void (*p2)();// 1、如何表示指针:用* 2、如何知道是函数:用()表示 3、函数指针是专用的,格式要求很强:参数类型,个数,返回值,就像数组指针一样
//如何给函数指针赋值
p2=printfWelcome;//这里不能写括号,函数名就像是地址,就像数组名一样就是地址
//如何通过函数指针调用函数
p2();//直接通过指针名字调用
(*p2)();//取内容再调用,(*指针名字)()
int (*padd)(int a,int b);//定义函数指针,形参可以省略
padd=add;
printf("结果是:%d\n",padd(1,2));
printf("结果是:%d\n",(*padd)(1,2));
system("pause");
return 0;
}
无类型的指针:
这里引入malloc函数
不对指针进行写操作就不用malloc给他开辟空间
给新定义的一个指针赋值(指针),也不用给新定义的指针malloc开辟空间
malloc的函数原型是:
extern void *malloc(unsigned int num_bytes);
这个函数返回的是无类型的指针。
头文件:#include <malloc.h> 或 #include <alloc.h> (注意:alloc.h 与 malloc.h 的内容是完全一致的。)
功能:分配长度为num_bytes字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
num_bytes表示大小是为无符号整型
一、函数声明(函数原型):void *malloc(int size);
说明:malloc 向系统申请分配指定size个字节的内存空间。
返回类型是 void* 类型,void* 表示未确定类型的指针。
C,C++规定,void* 类型可以强制转换为任何其它类型的指针。
二、malloc与new的不同点
从函数声明上可以看出。malloc 和 new 至少有两个不同:
new 返回指定类型的指针,并且可以自动计算所需要大小。
比如:
int *p;
p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);
或:
int* parr;
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
而 malloc 则必须由我们计算要字节数,并且在返回后强行转换为实际类型的指针。
int* p;
p = (int *) malloc (sizeof(int));
int* p; p++偏移4个字节
char* p; p++偏移1个字节
void* p; p++不知道偏移几个字节
/*int返回的是整型,void并不是没有返回值,
而是他的返回值是:无类型的,void本身也是一个类型(无类型)*/
第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int ) 来将强制转换。
第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成:int p = (int ) malloc (1);代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容全部被清空。malloc 也可以达到 new [] 的效果,申请出一段连续的内存,方法无非是指定你所需要内存大小。比如想分配100个int类型的空间:int p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下100个整数的内存空间。另外有一点不能直接看出的区别是,malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。
总结:
malloc()函数其实就在内存中找一片指定大小的空间,然后将这个空间的首地址范围给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址,这要看malloc()函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上连续的,而在物理上可以连续也可以不连续。对于我们程序员来说,我们关注的是逻辑上的连续,因为操作系统会帮我们安排内存分配,所以我们使用起来就可以当做是连续的。
实例演示(内含用户输入数组元素个数指针方式):
#include <stdio.h>
#include <stdlib.h>
//指针可以当做数组名来用,作为数组的首地址,严格的说不等于数组,但是可以认为它是个数组一样的使用而不产生任何问题。
//不过既然这样,那它应该算是个数组吧。所以,一般我们都用“动态数组”这种名字来称呼这种东西。
//malloc能操作的是程序中的堆空间,而普通的数组则是存放在栈空间里面的。
/* 堆空间是系统内存中的可用区域,和普遍意义上的“堆(Heap)”不同,基本上可以看作是由空闲内存组成的大链表。
嗯,操作系统怎么处理这东西不管了,反正你就可以认为堆空间是可用内存里的一片连续区域。malloc函数的作用就是从这一片内存中划出一块空间来。
你可以认为是malloc从内存中找到了一片可以安全存放数据的可用空间,这样你的数据就可以放在这片空间里面。这片空间的大小是你自己指定的。
通过malloc(字节数)这样简单的方法。为了找到这片空间,malloc函数会告诉你这片空间开头的地址,你可以把它赋值给一个变量存放起来。*/
int main()
{
//int a[3];
/* int* a=(int*)malloc(3*sizeof(int));//通过malloc开辟内存空间,并强制转换为int类型的指针
//a指向开辟连续内存空间的首地址
//a仅仅代表开辟的12字节的整型内存空间的首地址,所以可以作为数组名来为内存写值,如下:
int i;
for(i=0;i<3;i++){
a[i]=i;//i是整型,占用三个字节,malloc开辟的是连续的空间,
}
for(i=0;i<3;i++){
printf("数组打印:%d\n",a[i]);
}*/
int n,i;
printf("请输入数组的个数:\n");
scanf("%d",&n);
printf("n=%d\n",n);
int* a=(int*)malloc(n*sizeof(int));
if(a==NULL){
printf("开辟失败\n");
}
for(i=0;i<n;i++){
printf("请输入第%d个学生的成绩:\n",i+1);
scanf("%d",&a[i]);//注意要取地址
}
for(i=0;i<n;i++){
printf("第%d个学生的成绩是:%d \n",i+1,*a++);
}
system("pause");
return 0;
}
详解malloc函数的博客:malloc
什么是内存泄漏?如何避免?
内存泄漏:主要体现是程序刚跑起来很好,跑几个小时或者几天或者几周程序崩溃。
比如:
while(1){
sleep(1);//每隔一秒
int* p=(int*)malloc(1024);//开辟1M空间,malloc申请的空间,程序不会主动释放
//linux中的话,程序结束后,系统会回收空间,程序不结束,不释放
如何避免:
1、注意,循环中有没有一直申请开辟内存
2、及时合理的释放 free(p) p=NULL;若不写p=NULL可能会使p变成野指针
野指针:
是指在定义指针的时候没有将指针初始化,比如:int* p,p就是野指针,将p=NULL后就不是了
}
指针收官之面试题小测:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;//定义整形变量
int* p;//定义p为指向整型数据的指针变量
p=NULL;
int a[5];//定义数组a,它有5个元素
int* p[4];//定义指针数组p,它由4个指向整形数组的指针元素组成
int (*p)[4];//p为指向包含4个元素的一维数组的指针变量
void* p;//p是一个指针变量,基本类型为void(空类型),不指向具体的对象
int add(int,int);//add为返回整型的函数
int (*p)(int a);//p为指向数组的指针,该函数返回一个整型
int** p;//p是一个指针变量,他指向整型数据的指针变量
int* p();//p为一个指针的函数,该指针指向整型数据
system("pause");
return 0;
}
int add(int a,int b)//返回值是整型的函数
{
return a+b;
}