第六部分
指针(重点)
本部分中的内容有些绕,且需要一些其他学科(组成原理和操作系统)的知识
为数据库,JAVA,C++,数据结构打基础
------------------------------------------------------
指针和指针变量的关系
指针就是地址,地址就是指针
地址就是内存单元的编号
故,指针只是一个值
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但要注意
通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
热身程序
热身程序中的代码,只讲语法,不追究实际意义
程序一
//本代码只讲语法,不追究实际意义
#include <stdio.h>
int main()
{
int * p;//p是变量的名字,int*表示p变量存放的是int
/*
int * p 不表示定义了一个名字叫做*p的变量
int * p 中p是变量名,p变量的数据类型是int *类型
所谓int * 类型,实际就是存放int变量地址的类型
int p表示p只能存放整数*/
int i=3;
p=&i; //ok 存放int类型变量的地址
p=i; //error,因为类型不一致,p只能存放int类型变量的地址,不能够存放int变量的值
p=55;//error ,原因同上
return 0;
}
程序二
//本代码只讲语法,不追究实际意义
#include <stdio.h>
int main()
{
int * p;
int i=3;
int j;
p=&i;
/*
①p存放了i的地址,因此p指向i
②p不是i,i也不是p,更准确的说,修改p的值不影响i的值,修改i的值也不影响p的值
③如果一个指针变量指向了某个普通变量,则
*指针变量就完全等同与普通变量
例子:
如果p是个指针变量,并且p存放了普通变量i的地址
则p指向了普通变量i
*p 就完全等同与 i
或者说: 在所有出现*p的地方都可以替换为i
在所有出现i的地方都可以替换为*p
*p 就是以p的内容为地址的变量
*/
j=*p; //等价于j=i;
printf("i=%d,j=%d,*p=%d\n",i,j,*p);
return 0;
}
//i=3,j=3,*p=3
指针的重要性
表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是c语言的灵魂
指针的定义
地址就是内存单元的编号
从0开始的非负整数
范围:2的32次方(B)=的30次方*2的平方(GB)=4(GB) 即4G或[0到4G-1]
指针就是地址,地址就是指针
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
但要注意
通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
指针的本质就是一个操作受限(四则运算中只能减,其余不行)的非负整数
指针的分类
①基本类型指针
例:
//热身程序中的程序二
#include <stdio.h>
int main()
{
int * p;
int i=3;
int j;
p=&i;
j=*p;
printf("i=%d,j=%d,*p=%d\n",i,j,*p);
return 0;
}
//i=3,j=3,*p=3
常见错误
错误一
#include <stdio.h>
int main()
{
int * p;
int i=5;
* p=i; //本行错误,
printf("%d\n",*p);
return 0;
}
//出错
错误二
#include <stdio.h>
int main()
{
int i=5;
int *p;
int *q;
p=&i;
*q=p; //error,两个类型不一致
*q=*p;//error
p=q;//error, q为垃圾值,q赋值给p,p也变为垃圾直
printf("%d\n",*q);
/*q的空间是属于本程序的,所以本程序可以读写q的内容,
但若q内部是垃圾值,则本程序不能读写*p的内容
*q所代表的内存单元的控制权限并没有分配给本程序
所以运行到printf时,会立即出错
*/
return 0;
}
//出错
经典指针程序:互换两个数字
第一版:原方法
//可以互换
#include <stdio.h>
int main()
{
int a=3;
int b=5;
int t;
t=a;
a=b;
b=t;
printf("a=%d,b=%d\n",a,b);
return 0;
}
//结果 a=5,b=3
第二版函数
//无法互换
#include <stdio.h>
void huhuan(int a,int b)
{
int t;
t=a;
a=b;
b=t;
return;
}
int main()
{
int a=3;
int b=5;
huhuan(a,b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=3,b=5
//数字未进行互换,改变的只是形参的值,跟实参无关
//且void执行完后,形参释放,故只能输出实参的值
第三版指针原程序
//无法互换
#include <stdio.h>
void huhuan2(int *p,int *q)
{
int * t;//写int t是错误的,类型不一致,若要互换p和q值,必须为int *。
t=p;
p=q;
q=t;
return;
}
int main()
{
int a=3;
int b=5;
huhuan2(&a,&b);
//写*p,*q是错误的,因为该值在void中定义,并未留在main中定义
//写a,b也是错误的
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=3,b=5
//只换了地址,地址内的内容仍没有变化,形参的改变改不了实参的值
第四版指针修改程序
//可以互换
#include <stdio.h>
void huhuan3(int *p,int *q)
{
int t; //若要互换*p和*q的值,t必须定义为int,写int * t是错误的,类型不一致
t=*p;//p是int *类型,*p是int型
*p=*q;
*q=t;
return;
}
int main()
{
int a=3;
int b=5;
huhuan3(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=5,b=3
第五版指针加声明程序
指针加声明,形参名可以写成下列的样子,
//与第四版相比,互换了void和main的位置,加了声明
//可以互换
#include <stdio.h>
void huhuan3(int *,int*);
int main()
{
int a=3;
int b=5;
huhuan3(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
void huhuan3(int *p,int *q)
{
int t; //若要互换*p和*q的值,t必须定义为int,写int * t是错误的,类型不一致
t=*p;//p是int *类型,*p是int型
*p=*q;
*q=t;
return;
}
//a=5,b=3
星号的含义
*号:
①乘法
②定义指针变量
int * p ; //定义了一个名字叫p的变量,int *表示p只能存放int的地址
③指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则*p表示:已p的内容为地址的变量
复习前面的内容
与互换连个数字代码基本一致,可依据自身情况选择性查看
基本类型指针
//基本类型指针1
#include <stdio.h>
int main()
{
int * p;//等价于int *p,也等价于int* p; 但最好还是写第一个
int i=5;
char ch='A';//必须用符号框起来,否则会认为A是一个变量
p=&i;//ok *p以p的内容为地址的变量
*p=99;
/*
p=&ch;//error 字符变量地址
p=ch;//error
p=5;//error p存放指针的地址,故错误
*/
printf("i=%d,*p=%d\n",i,*p);
return 0;
}
//i=99,*p=99
//基本类型指针2
#include <stdio.h>
void swap_1(int i,int j) //i,j形参 i,j和 a,b为不同的变量
{
int t;
t=i;
i=j;
j=t;
}
int main()
{
int a=3;
int b=5;
swap_1(a,b); //a,b实参
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=3,b=5 未进行互换
//检测实参和形参是否是同一个变量
#include <stdio.h>
void f(int i)
{
i=99;//实参i和形参i不是一个变量
}
int main()
{
int i=6;
printf("i=%d\n",i);
f(i);
printf("i=%d\n",i);
return 0;
}
//i=6 i=6
//基本类型指针2修改一版
#include <stdio.h>
void swap_2(int * p,int * q)//形参名字是p和q,接收实参数据的是p和q,而不是*p和*q
{
int * t;//不可写int t,与p q类型不一致
t=p;
p=q;
q=t;
}
int main()
{
int a=3;
int b=5;
swap_2(&a,&b);//实参只能发送给变量,故上面发给p或q,而不是*p或*q
//不可写为(a,b),发送过去的是地址,pq存放的是整型地址而非整型变量
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=3,b=5 未进行互换 实参和形参为不同变量,形参的交换与实参没有关系
//基本类型指针2修改二版
#include <stdio.h>
void swap_3(int * p,int * q)//形参名字是p和q,接收实参数据的是p和q,而不是*p和*q
{
int t;//不可写int * t,与*p *q类型不一致
t=*p;
*p=*q;
*q=t;
}
int main()
{
int a=3;
int b=5;
swap_3(&a,&b);//实参只能发送给变量,故上面发给p或q,而不是*p或*q
//不可写为(a,b),发送过去的是地址,pq存放的是整型地址而非整型变量
printf("a=%d,b=%d\n",a,b);
return 0;
}
//a=5,b=3
通过指针为什么可以使被调函数修改主调函数多
函数返回一个值
//不使用指针, 只能改变一个值 ,效率降低
#include <stdio.h>
int f(int i,int j)
{
return 100;
//return 88; error 返回100后,本行不会执行
}
int main()
{
int a=3,b=5;
a=f(a,b);
b=f(a,b);
printf("a=%d,b=%d\n",a,b);
}
//a=100,b=100
指针使函数返回一个以上的值
#include <stdio.h>
int f(int i,int j)
{
return 100;
}
void g(int * p,int * q)
{
*p=1;
*q=2;
}
int main()
{
int a=3,b=5;
g(&a,&b);
printf("a=%d,b=%d\n",a,b);
}
//a=1,b=2
如何通过被调函数修改主调函数普通变量的值
①实参必须为该普通变量的地址()
②形参必须为指针变量
③在被调函数中通过
*形参名=...
的方式就可以修改主调函数相关变量的值
一个和多个原理是一致的,若要修改多个,就将多个普通变量的地址发送过去
②指针和数组
本章打好基础,今后学数据结构的链表就能搞清楚以及其他语言里有关容器的内容
指针和一维数组(重点)
▶一维数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
#include <stdio.h>
int main()
{
int a[5];//a是数组名,5是数组元素的个数,元素就是变量 a[0]-a[4]
//int a[3][4]; 3行4列 a[0][0]是第一个元素 a[i][j]即第i+1行j+1列
int b[5];
//a=b; error ,a是常量,不可赋给常量
printf("%#X\n",&a[0]);
printf("%#X\n",a);
return 0;
}
/*输出结果
0X33A2B9D0
0X33A2B9D0*/
▶下标和指针的关系
如果p是个指针变量,则
p[i]永久等价于*(p+i)
确定一个一维数组需要几个参数[如果一个函数要处理一个一维数组,则需要接收该数组的哪些信息]
需要两个参数:
数组第一个元素的地址
数组的长度
①代码
//确定一个一维数组需要两个参数,缺一不可
#include <stdio.h>
//函数可以输出任何一个一维数组的内容
void f(int * pArr,int len)//字符串汇总\0可作为结束标记,但数组中存放的任何一个值都可能为有效值,故确定一个数组需要两个信息
{
int i;
for(i=0;i<len;++i)
printf("%d ",*(pArr+i));//*pArr *(pArr+1) *(pArr+2)
printf("\n");
}
int main()
{
int a[5]={1,2,3,4,5};
int b[6]={-1,-2,-3,4,5,-6};
int c[100]={1,99,22,33};//c语言中,没有赋值,会默认为0
f(a,5);//a是int *
f(b,6);
f(c,100);
return 0;
}
/*输出结果
1 2 3 4 5
-1 -2 -3 4 5 -6
1 99 22 33 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
②代码
//一定要明白,void中的pArr[3]和两个输出中的a[3]是同一个变量
#include <stdio.h>
void f(int * pArr,int len)
{
pArr[3]=88;
}
int main()
{
int a[6]={1,2,3,4,5,6};
printf("%d\n",a[3]);//结果4
f(a,6);
printf("%d\n",a[3]);//结果88
printf("%d\n",&a[3]);//结果为418916956,a[3]的地址
return 0;
}
/*输出结果
4
88
418916956
*/
代码③
#include <stdio.h>
void f(int * pArr,int len)
{
int i;
for(i=0;i<len;++i)
printf("%d ",* (pArr+i));//* (pArr+i)等价于pArr[i]等价于b[i]也等价于* (b+i)
printf("\n");
}
int main()
{
int a[6]={1,2,3,4,5,6};
int b[6]={-1,-2,-3,4,5,-6};
int c[100]={1,99,22,33};
f(b,6);
return 0;
}
/*输出结果
-1 -2 -3 4 5 -6
*/
复习上节课知识
#include <stdio.h>
void OutArr(int * pArr,int len)
{
pArr[2]=10;//pArr[2] == * (pArr+2) == a[2] == *(a+2)
}
int main()
{
int a[5]={1,2,3,4,5};
//a=&a[2];//error a为指针,为常量,值不可改变
printf("%#X,%#X\n",a,&a[0]); //a=&a[0] a!=a[0]
printf("%d\n",a[2]);
OutArr(a,5);
printf("%d\n",a[2]);
return 0;
}
/*输出结果
0X5A7E6F80,0X5A7E6F80
3
10
*/
#include <stdio.h>
void OutArr(int * pArr,int len)
{
//pArr[2]=10;//pArr[2] == * (pArr+2) == a[2] == *(a+2)
int i;
for(i=0;i<len;++i)
printf("%d\n",pArr[i]);
}
int main()
{
int a[5]={1,2,3,4,5};
//a=&a[2];//error a为指针,为常量,值不可改变
//printf("%#X,%#X\n",a,&a[0]); //a=&a[0] a!=a[0]
//printf("%d\n",a[2]);
OutArr(a,5);
//printf("%d\n",a[2]);
return 0;
}
/*输出结果
1
2
3
4
5
*/
//要想改变 一个变量的值,需发送这个变量的地址
#include <stdio.h>
void f(int * p)
{
*p=10;//i=10
}
int main()
{
int i=5;
f(&i);
printf("i=%d\n",i);
return 0;
}
/*输出结果
i=10
*/
▶指针变量的运算
指针变量不能相加,不能相乘 不能相除,只能减
如果两个指针变量指向的是同一块连续空间中的不同存储单元
则这两个指针变量才可以相减
#include <stdio.h>
int main()
{
int i=5;
int j=10;
int * p=&i;
int * q=&j;
int a[5];
p=&a[1];
q=&a[4];
printf("p和q所指向的单元相隔%d个单元\n",q-p);
return 0;
}
/*输出结果
p和q所指向的单元相隔3个单元
*/
一个指针变量到底占几个字节(非重点)
预备知识:sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(int)=4 sizeof(char)=1 sizeof(double)=8
sizeof(变量名)
功能:返回值是该变量所占的字节数
假设p指向char类型变量 1B
假设q指向int类型变量 4B
假设r指向double类型变量 8B
p q r 本身所占的字节数是否一样? 是一样的
#include <stdio.h>
int main()
{
char ch='A';
int i=99;
double x=66.6;
char * p=&ch;
int * q=&i;
double * r=&x;
printf("%d %d %d\n",sizeof(p),sizeof(q),sizeof(r));
return 0;
}
//4 4 4
总结:
一个指针变量,无论它指向的变量占几个字节,该指针变量本身占四个字节
一个变量的地址使用该变量首字节的地址来表示
专题
动态分内存分配[重点难点]
▶传统数组的缺点
1.数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a[5];//OK
int len =5;int a[len]; //error
2.传统形式定义的数组,该数组的内存,程序员无法手动释放
在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放
3.数组长度一旦定义,其长度不能再更改
(或者说数组的长度不能在函数运行的过程中,动态的扩充或缩小)
4.A函数定义的数组,在A函数运行期间可以被其它函数使用,但A函数运行完毕之后,A函数中的数组将无法再被其它函数使用
传统方式定义的数组不能跨函数使用
//传统数组的缺陷
#include <stdio.h>
void g(int * pArr,int len)
{
pArr[2]=88;//pArr[2]== a[2]
}
void f()
{
int a[5]={1,2,3,4,5};//20个字节的存储空间,程序员无法手动变成释放它,它只能在本函数运行完毕时,由系统自动释放
g(a,5);
printf("%d\n",a[2]);
}
int main()
{
f();
return 0;
}
▶为什么需要动态分配内存
动态数组很好的解决了传统数组的这4个缺陷
传统数组也叫静态数组
▶动态内存分配举例_ 动态数组的构造
malloc的使用
malloc(200);
char * 200个变量
int * 50 个变量
double * 25个变量
//malloc第一种用法
//无实际含义
//malloc是memory(内存)allocate(分配)的缩写
#include <stdio.h>
#include <malloc.h>
int main()
{
int i=5;//分配了4个字节 静态分配
int * p=(int *)malloc(4);//强制转换为int
/*
1.要使用malloc函数,必须添加malloc.h这个头文件
2.malloc函数只有一个形参,并且形参是整型
3.malloc(4)中的4表示请求系统为本程序分配4个字节
4.malloc函数只能返回第一个字节的地址
5.int * p=(int *)malloc(4);本行分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
6.p本身所占的内存是静态分配的,p所指向的内存是动态分配的
*/
*p=5;//*p代表的就是一个int变量,只不过*p这个整型变量的内存分配方式和“int i=5;”中i变量的分配方式不同
free(p);//free(p)表示把所指向的内存给释放掉,p本身内存是静态的,不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时自动释放
printf("喵喵喵\n");
return 0;
}
//malloc第二种用法
#include <stdio.h>
#include <malloc.h>
void f(int *q) //q是p的一份拷贝/副本
{
//*p=200;//error,f中没有p变量
//q=200;//error ,q为指针类型,只能存放int类型变量的地址。200只是个整数
//**q=200; //error q存放四个字节的地址 *q代表这四个字节
*q=200;
//free(q);//error,把q所指向的内存释放掉,则在最后的printf中的*p无法再使用
}
int main()
{
int * p =(int *)malloc(sizeof(int));//sizeof(int)返回值是int所占的字节数
*p=10;//以p的内容为地址的变量
printf("%d\n",*p);//10
f(p);
printf("%d\n",*p);//200
return 0;
}
/*
10
200
*/
//动态一维数组实例
#include <stdio.h>
#include <malloc.h>
int main()
{
int a[5];//如果int占4个字节的话,则本数组总共包含有20个字节,每四个字节被当做了一个int变量
int len;
int *pArr;
int i;
printf("请输入你要存放的元素的个数:");
scanf("%d",&len);//5
pArr=(int *)malloc(4 * len);//类似于 int pArr[len];
//上一行,动态构造了一个一维数组,这一维数组的长度是len,该组的数组名是pArr,该数组每个元素是int类型
//对一维数组进行操作
for(i=0;i<len;++i)
scanf("%d",&pArr[i]);
//对一维数组进行输出
printf("一维数组的内容是:");
for(i=0;i<len;++i)
printf("%d\n",&pArr[i]);
free(pArr);//释放掉动态分配的数组
return 0;
}
/*
请输入你要存放的元素的个数:5
1 2 3 4 5
一维数组的内容是:1
2
3
4
5
*/
//复习
# include <stdio.h>
# include <malloc.h>
//可以成功修改11行动态分配的四个字节的内容
void f(int *q)//发送p 写int *
{
*q=10;
}
void g(int **p)//发送p的地址 写int **
{
}
int main(void)
{
int *p=(int *)malloc(4); //11行
//*p=10;
printf("*p=%d\n",*p);//垃圾数字
//f(p); //ok
g(&p);//p是int *类型,&p是int **
printf("*p=%d\n",*p);//
return 0;
}
▶静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈分配的(栈内容在数据结构课程中)
动态内存是由程序员手动分配,手动释放
动态内存是在堆分配的
▶跨函数使用内存的问题
先看⑤多级指针内容再回到这个部分
//静态变量不能跨函数使用 此程序可运行,但不可这样写
#include <stdio.h>
void f(int **q)//q也是个指针变量,无论q是什么类型的指针变量,都只占用4个字节
{
int i=5;
//*q等价于p q和**q都不等价于p
//*q=i;//error 因为这样写等价于p=i,所以是错误的
*q=&i;//p=&i;
}
int main(void)
{
int *p;//13行
f(&p);
printf("%d\n",*p);//本语句语法没有问题,但逻辑上有问题,静态空间在栈里面分配,函数中止时会释放
return 0;
}
//运行结果
//5
//动态内存可以跨函数使用
#include <stdio.h>
#include <malloc.h>
void f(int **q)//q也是个指针变量,无论q是什么类型的指针变量,都只占用4个字节
{
*q=(int *)malloc(sizeof(int));//6行 sizeof(数据类型)返回值是该数据类型所占的字节数
//等价于p=(int *)malloc(sizeof(int));
//q=5;//若不赋值,是垃圾值
//*q=5;//p=5;
**q=5;//*p=5;
}
int main(void)
{
int *p;
f(&p);
printf("%d\n",*p);//指向6行,动态分配是在堆里面分配,在堆中,函数终止(出栈)时不会释放
return 0;
}
//运行结果
//5
//计算机二级题目
下列程序中,能够通过调用函数fun,使main函数中的指针变量p指向一个合法的整型单元的是(C)
A)main()
{ int *p;
fun(p);//p值未改
...
}
int fun (int *p)
{ int s;
p=&s;
}
B)main()
{ int *p;
fun(&p);
...
}
int fun (int **q)
{ int s;
*q=&s;//s地址可发送给p,但执行完后变量不存在
}
C)#include<stdlib.h>
main()
{
int *p;
fun(&p);
...
}
int fun (int **q)
{
*q=(int *)malloc(4);//*q是p,
}
D)#include<stdlib.h>
main()
{
int *p;
fun(p);
...
}
int fun (int *p)
{
p=(int *)malloc(sizeof(int));
}
指针和二维数组(难点)
③指针和函数
④指针和结构体
结构体有助于学习JAVA中的类
下一章
⑤多级指针
#include <stdio.h>
int main()
{
int i=10;
int * p=&i;
int ** q= &p;
int *** r=&q;
//r=&p; //error 因为r是int ***类型,r只是存放int ***类型变量的地址
printf("i= %d\n",***r);
return 0;
}
// 运行结果
//i= 10
变量 | i | p | q | r |
存放 | 10 | 1000H | 2000H | 3000H |
地址 | 1000H | 2000H | 3000H | 4000H |
类型 | int | int * | int ** | int *** |
用r表示 | ***r | **r | *r | r |
//掌握多级指针,为跨函数使用内存打基础
void f(int **q)
{
//q是p的地址 *q就是p
}
void g()
{
int i=10;
int * p=&i;
g(&p); //p是int * 类型,&p是int **类型
}
int main(void)
{
g();
return 0;
}