指针

1.初识指针

构造一个指针

int *p

p是变量的名称
int*表示p存放的是int类型变量的地址

double *q

q是变量的名称
double*表示q存放的是double类型变量的地址

变量类型 *指针变量 

用这种形式来定义存放各种类型变量的地址

指针就是地址,地址就是指针
地址就是内存单元的编号值
指针变量就是存放地址的变量
通俗来说就是:
杯和水的关系
放水的杯子
放东西的东西

指针和指针变量是两个不同的概念
但是通常我们叙述时,会把指针变量简称为指针

	int i;//普通变量
	int *p;//指针变量
	p = &i;
	&:取地址符 
	*p就是以p的内容 (i的地址)为地址的变量

指针的重要性

1.指针表示一些复杂的数据结构
2.快速地快递数据,减少内存的消耗
(结构体输入输出时优势可见)
3.使函数返回一个以上的值
4.能直接访问硬件
5.能够方便的处理字符串
6.是理解面向对象语言中引用的基础

指针的定义

地址:

  • 内存单元的编号
  • 从零开始的非负整数
  • 范围:4G【0—4*1024-1】
C  —— **控制总线**控制方向————————— 内
P  —— **数据总线**实现传递————————  存
U  —— **地址总线**(2^32)确定位置——  条

指针:

  • 指针就是地址,地址就是指针

  • 指针变量就是存放地址的变量,或者说是存放内存单元编号的变量

  • 指针的变质就是一个操作受限的非负整数

  • 指针只能做减法运算

    编号只有减法才有意义 即门牌号相减

*的含义

  1. 乘法
  2. 定义指针变量
  int*p;
  //定义了一个名字叫p的指针变量,int*p表示只能存放int类型的地址
  1. 指针运算符 <=> 取地址的逆运算符
    p = &i 保存了i的地址
    *p = i 地址索引发现p指向i

该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量,则*p表示 以p的内容 为地址的变量

如何通过被调函数修改主调函数普通变量的值

void swap_1(int a ,int b){
	int t;
	t=a;
	a=b;
 	b=t;
  //发生改变的是形参的ab 运行完被释放了 
  return a;
}
void swap_2(int*p,int*q){
	int *t;//如果要呼唤p和q的值,t必须是int*,不能是int 
	t=p;
	q=t;
	p=t;//互换家庭地址?但里面的人没有变 
}
void swap_3(int*p,int*q){
//如果要互换p和q的值,t必须是int,不能是int* 
	int t=*p;
	*p=*q;
	*q=t;	
}
  1. 实参必须为普通变量的地址
  2. 2形参必须为指针变量
  3. 在被调函数中通过 *形参名 的方式就可以修改主调函数相关变量的值
int main(){
  int a=3;
  int b=5;
  
 swap(a,b);
  
  printf("a=%d b=%d",a,b);
  
  return 0; 
} 

指针的分类

  • 1.基本类型指针
  • 2.指针和数组
  • 3.指针和函数
  • 4.指针和结构体
  • 5.多级指针
{
	int i = 5;
	int* p;
	int* q;
	*p = i;
	//程序定义了p i这两个空间,用5修改读取了一个不属于本程序的单元
	p = &i;
	*q = p;//*q是整型类型,p是指针类型 类型不一致 语法错误 报错
	*q = *p;//error p里面没有赋值
	p = q;//error q是垃圾值,q赋给p,p也变成垃圾值
	q的空间是属于本程序的,所以本程序可以读写q的内容,
	但是如果q内部是垃圾值,则本程序不能读写*p的值
	因为*q所代表的内存单元的控制权限并没有分配给本程序
}

2.指针和数组

指针和一维数组

一维数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址

下标和指针的关系

如果p是一个指针变量,则
p[i] 永远等价于 *(p+1)

确定一个一维数组需要几个参数

需要两个参数:
1.数组第一个元素的地址
2.数组长度

//f函数可以输出任何一个一维数组的内容 (遍历!)
void f(int *pAr,int len) {
	/*
	/0是字符串结束的标志  
	但是在数组中 每一个值都可能是有效的值 
	所以需要 一个初始的单元告诉你是什么 再确定长度
	*/
	int i;
	for (i = 0; i < len;++i) {
		printf("%d\t", *(pAr + i));    //等价于pAr[i]
	
	 }
}
int main() {
	int a[5] = { 1,2,3,4,5 };
	
	f(a,5);
	
	return 0;
}

指针变量的运算

  • 指针变量不能相加 不能相乘 不能相除

  • 如果两个指针变量指向的是同一块联系空间中的不同的存储单元,则这两个指针变量才可以相减

  • 门牌号要在同一个小区 才有意义!

一个指针变量到底占几个字节?

  • sizeof
  1. sizeof(数据类型)

    功能:返回值就是该数据类型所占的字节数
    例子:sizeof(char)=1
    sizeof(int)=4
    sizeof(double)=8

  2. sizeof(变量名)
    int i ; sizeof(i)

    功能:返回值就是该变量所占的字节数

两种方法都可

p 指向char类型变量(1个字节)
p 指向int类型变量(4个字节)
p 指向double类型变量 (8个字节)
p q r本身所占的字节是否一样 ?
答案:一样
语言的定义本身就是有意义的

int main(void) {
	    char ch = 'A';
		int i = 99;
		double x = 66.6;

		char* p = &ch;
		int* q = &i;
		double* r = &x;

		printf("%d %d\n",sizeof(ch),sizeof(p));
		printf("%d %d\n",sizeof(i), sizeof(q));
		printf("%d %d\n",sizeof(x), sizeof(r));
}

8位=1个字节 32位=4个字节
每个地址都用32根地址总线表示 即4字节
地址用4个字节表示
不管是 输出最大的地址还是 输出最小的地址
这32根线都是会输出的(0或者)1
不管是输出最大还是最小,这32根线都是要输出的
32bit换算成字节为4

  • 一个指针变量,无论它指向的实际变量占几个字节,该指针变量本身只占4个字节

  • 一个变量的地址是由该变量的首字节的地址来表示

如何通过指针改变数组某个位置上的值

void f(int *pAr,int len) 
{
	pAr[3]=88;
}
int main() 
{
	int a[5] = { 1,2,3,4,5 };
	printf("%d\n",a[3]);
	f(a,5);
	printf("%d\n",a[3]);
	
	return 0;
}

指针和二维数组

int main() {
	int a[5];
//a是数组名 5是数组元素的个数 元素就是变量 a[0]--a[4]
	int a[3][4];
//a是数组名 3*4=12是数组元素的个数 
//元素是a[0][0]--a[2][3]

	//a=b;//a是常量,不可赋值
	printf("%#x", &a[0]);
	//#x以十六进制输出
	printf("%#x", a);
	return 0;
}
  • a[ 0 ][ 0 ]是第一个元素
    a[ i ][ j ]是 第i+1行 第j+1列

多级指针(指针套娃)

int i = 10 ;
int *p = &i ;

p是int*类型
只能存放int类型变量的地址

int **q = &p;

q是int**类型
所谓int**类型是指q只能存放int*类型变量的地址

int ***r = &q;

r是int***类型
所谓int***类型是指r只能存放int**类型变量的地址

printf("i=%d\n",*p);     //i=10
printf("i=%d\n",**q);    //i=10
printf("i=%d\n",***r);   //i=10

动态内存分配【重点难点】

静态内存 (以传统数组为例)的缺点

  1. 数度长度必须事先制定,且只能是常整数,不能是变量

     int a[5];     //ok
      int len=5;   int a[len];    //error 
    
  2. 传统形式定义的数组,该数组的内存程序员无法手动释放
    数组一旦定义,系统为给数组分配的存储空间就会一直存在, 除非该数组所在的函数运行结束
    在一个函数运行期间,系统为该函数所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放

void g(int* pAr, int len)
 {
	pAr[2] = 8;//pAr[2]==a[2]
}

void f(void) 
{
	int a[5] = { 1,2,3,4,5 };/*
	20个字节的存储空间程序员无法手动释放它
	它只能在本函数运行完毕时由系统自动释放
           */

	g(a, 5);
	printf("%d\n", a[2]);
}
  1. 数组的长度不能在函数运行的过程中动态的扩充或缩小 数组的长度一旦定义,其长度就不能再改变
  2. A函数定义的数组,在A函数运行期间可以被其他函数使用 但A函数运行完毕后,A函数的数组将无法在被其他函数调用,传统定义的数组不能跨函数使用

为什么需要动态分配内存呢?

  • 动态数组很好地解决了传统数组的四个缺陷(传统数组也叫做静态数组)

动态内村分配举例_ 动态数组的构造

#include<stdio.h>
#include<malloc.h>//插入malloc的头文件

int main(void)
{
	int i = 5;//分配了4个字节 静态分配
	int* p = (int*)malloc(4);//强制类型转化,值有 类型有
	
	*p = 5;//*p就是一个int变量,只不过*p这个整型变量的内存分配和int i的分配方式不一样
	free(p);
	/*free(p) free point 表示把所指向的内存给释放掉
			p本身的内存是静态的,不能由程序员手动释放,
			p本身的内存只能由p变量所在的函数运行终止时由系统自动释放*/

	return 0;
}

free()函数原型

	void free(void *p);

释放可以节约内存,保证内存的安全管理

1.要使用malloc函数,必须添加malloc.h这个头文件
malloc 是memory(内存)allocate(分配)的缩写
2.malloc函数只有1个形参,并且形参是整型类型
3. 4实参表示请求系统为本程序分配4个字节
4. malloc函数 只能返回一个字节的地址
5. 分配了8个字节(p变量占4个字节,p指向的内存也占了4个字节)
6. p本身所占的内存是静态的,p所指向的内存是动态的

#include<stdio.h>
#include<malloc.h>//插入malloc的头文件
/*
malloc 是memory(内存)allocate(分配)的缩写
*/
void f(int*q)//q是p的一份拷贝一份副本
{
	*q = 200;
	//free(q);//把q指向的内存释放掉
}
int main(void)
{
	int i = 5;
	//分配了4个字节 静态分配
	int* p = (int*)malloc(sizeof(int));
	//sizeof(int)的返回值是int所占的字节数
	*p = 10;
	printf("%d\n", *p);
	f(p);
	printf("%d", *p);
	return 0;
}

构造一维数组

	int a[5];//如果int占4个字节的话,则本数组总共含有20个字节,每4个字节相当于1个int变量来使用
	int* p;
	int len;
	printf("请输入你要存放的元素的个数:");
	scanf("%d", &len);
	p = (int*)malloc(4 * len);
	//相当于 int p[len]; 这一维数组的长度是len,该数组的每个元素都是int类型
	q=(double*)malloc(8 * len)realloc(p,100); 
	再分配 将内存字节改为100 动态地改变内存字节数

realloc动态地扩充 动态地缩小

对动态一维数组进行赋值

for (i = 0; i < len; ++i) {
		scanf("%d", &p[i]);
	}

对动态一维数组进行输出

for (i = 0; i < len; ++i) {
		print("%d", p[i]);
	}

free (p); 可以手动释放掉动态分配的数组,静态数组则不可
输入输出操作上等同于静态数组
跨函数使用内存的问题

动态内存和静态内存的比较

  • 静态内存是由系统自动分配,由系统自动释放
    静态内存是在找分配的
  • 动态内存是由程序员手动分配,手动释放
    动态内存是在堆分配的

跨函数使用内存的问题

  • 动态内存可以跨函数运作
  • 动态内存在函数执行完毕之后仍然可以被其他函数使用
  • 静态内存不可以跨函数运作
  • 静态内存在函数执行期间可以被其他函数使用,执行完毕之后就不能再被其他函数使用了
{
	int i = 5;
	//q是p的地址,那么*q=p
	*q = &i;
	// *q=i error 等价于p=i,这样写是错误的,因为i是int类型,p是int*类型

}

int main() {
	int* p;
	f(&p);
	//要想改变p的值,必须发送p的地址给f函数
	printf("%d", *p);
	//本语句语法没有问题,但逻辑上有问题,访问了一个不该被访问的地址
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值