C语言指针

本文详细介绍了C语言中的指针概念,包括指针作为地址、指针变量的声明和使用,以及指针在数据结构、函数调用和数组操作中的作用。还讨论了动态内存分配,如malloc和realloc函数的使用,以及静态和动态内存的区别。同时,文章提到了多级指针和跨函数使用内存的问题,并强调了动态内存分配在函数间共享数据的重要性。
摘要由CSDN通过智能技术生成

C语言指针(参考c语言郝斌的视频)

指针就是地址,地址就是指针 地址是内存单元的编号

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

#include <stdio.h>

int main(void)
{
	int * p;   //p是变量的名字,int *表示 p变量存放的是int类型变量的地址
				//int * p; 不表示定义了一个名字叫做*p的变量
				//int * p; 应该这样理解;p是变量名,p变量的数据类型是int * 类型
				// 所谓 int * 类型 实际就是存放int变量地址的类型
	int i = 3;
	p = &i; //OK
			/*
				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 的内容为地址的变量
			
			*/
		
	//p = 1; //error ,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值   
	//p = 55; //error 原因同上

	return ;
}

指针的重要性

表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础

什么是地址

内存单元的编号
从零开始的非负整数
范围:4G 【0--4G-1】相对32的来说

什么是指针

指针就是地址,地址就是指针
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际他们是两个不同的概念
指针的本质就是一个操作受限的非负整数

基本类型的指针常见错误解析

#include <stdio,h>

int mian(void)
{
	int i = 5;
	int * p;
	int * q;
	
	p = &i;	
	//*q = p; 	//error 语法编译出错, p是int *类型,*p是int类型
	//*q = *p;	//error  q没有赋值
	p = q;		//q是垃圾值,q赋给p,p也变成垃圾值
	printf("%d\n", *q);  /*
							q的空间是属于本程序的,所以本程序可以读写q的内容,
                            但是如果q内部是垃圾值,则本程序不能读写*q的内容
                            因为此时*q所代表的内存单元的控制权限并没有分配给本程序
                            所以本程序运行到13行时就会立即出错
                          */
                          
    return 0;                      
}

经典指针程序——互换两个数字

#include <stdio.h>

void swap(int * num1, int * num2)
{
	/*	这个方式无法将两个变量的值交换,因为此处只是将swap函数里num1和num2存放的地址交换
		但是,main函数里num1和num2的地址仍是原先的,所以并没有改变main函数里的num1和num2的值
	int *temp;
	temp = num1;
	num1 = num2;
	num2 = temp;
	*/


	int temp;
    temp = *num1;
    *num1 = *num2;
    num2 = *num1;
}

int main(void)
{
	int num1 = 3;
	int num2 = 5;
	
	swap(&num1, &num2);
	printf("num1 = %d\r,num2 = %d\n",num1,num2);
	
	return 0;
}

注:* 的含义
    1. 乘法
    2. 定义指针变量
        int *p;  //*p 以p的内容为地址的变量
        //定义了一个名字叫p的变量,int*表示p只能存放int类型变量的地址
    3. 指针运算符
        该运算符放在已经定义好的指针变量的前面如果p是一个已经定义好的指针变量
        则*p表示以p的内容为地址的变量

指针返回一个以上的值

/*如何通过被调函数修改主调函数普通变量的值
1、实参必须为该普通变量的地址
2、形参必须为指针变量
3、在被调函数中通过  *形参名 = ...  的方式就可以修改主调函数相关变量的值
*/
#include <stdio.h>

void func(int * p, int *q)
{
	*p = 20;
	*q = 30;
}

int main(void)
{
	int num1 = 3;
	int num2 = 5;
	func(&num1, &num2);
	printf("%d %d\n",num1,num2);
	return 0;
}

指针和数组

指针和一维数组

一维数组名
	一维数组名是个指针常量
	它存放的是一维数组第一个元素的地址
	
下标和指针的关系
	如果p是个指针变量,则 p[i] 永远等于 *(p+i)
	确定一个一维数组需要几个参数
        需要两个参数:
        数组第一个元素的地址数组的长度

指针变量的运算
	指针变量不能相加,不能相乘,也不能相除
	如果两个指针变量指向的是同一块连续空间中的不同存储单元,
	则这两个指针变量才可以相减

一个指针变量到底占几个字节
    预备知识:
        sizeof(数据类型)
        功能:返回值就是该数据类型所占的字节数
        例子: sizeof(int) = 4 
        	sizeof(char) = 1
        	sizeof (double)= 8
        	
        	sizeof(变量名)
			功能:返回值是该变量所占的字节数
			
    假设p指向char类型变量(1个字节)
    假设q指向int类型变量(4个字节)
    假设r指向double类型变量(8个字节)
    p q r本身所占的字节数是否一样? 一样的
		总结:一个指针变量,无论它指向的变量占几个字节
			 该指针变量本身只占 四个字节
			 一个变量的地址使用该变量首字节的地址来表示
			 
#include <stdio.h>

int main(void)
{
	int a[5] = {1,2,3,4,5};
	//a = &a[2];  //error a是一个常量
 	printf("%#x %#x\n", a, &a[0]); //a == &a[0]
	return 0;
}




#include <stdio.h>

void func(int *, int);

int main(void)
{
	int arr[6] = {0,1,2,3,4,5};
	func(arr,6);
//	for(int i=0; i<6; i++)
//	{
//		printf("a[%d] = %d\n", i, *(arr+i));  
//      *(pArr+i) 等价与 pArr[i] 也等价于arr[i] 也等价于*(b+i)
//	}
	return 0;
}

void func(int *pArr, int len)
{
	int i;
	for(i=0; i<len; i++)
	{
		printf("a[%d] = %d\n", i, *(pArr+i));  
		//*(pArr+i) 等价与 pArr[i] 也等价于arr[i] 也等价于*(b+i)
	}
}

#include <stdio.h>

int main(void)
{
	int a[5];
	int * p;
	int * q;
    p = &a[1];
    q = &a[4];
    
    printf("p和q所指向的单元相隔%d个单元\n", q-p); //连续的空间才有意义
}


#include <stdio.h>

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 %d\n", sizeof(p), sizeof(q),sizeof(r)); 
    
    return 0;
}

动态内存分配

传统数组的缺点

传统数组的缺点:
1.数组长度必须事先制定,且只能是常整数,不能是变量
    例子:
        int a[5];//OK
        int len = 5; int a[len]; // error  
        //在devc++编译器中有无元素可以,
        //在linux 中当int a[len]后面没有元素时可以通过编译,有元素则不行
2.传统形式定义的数组,该数组的内存程序员无法手动释放
   在一个函数运行期间,系统为该函数中数组所分配的空间
   会一直存在,直到该函数运行完毕时,数组的空间才会释放
3.  数组的长度一旦定义,其长度就不能在更改
	数组的长度不能在函数运行的过程中动态的扩充或缩小
4. A函数定义的数组,在A函数运行期间可以被其它函数使用,
    但A函数运行完毕之后,A函数中的数组将无法在被其他函数使用(已经被释放)
    传统方式定义的数组不能跨函数使用


	 

数组无元素时

image-20230402160939575

image-20230402161117793

数组有元素时

image-20230402161334385

image-20230402161414004

malloc函数

//为什么需要动态分配内存
//	动态数组很好的解决了传统数组的这4个缺陷
//	传统数组也叫静态数组
	
#include <stdio.h>
#include <malloc.h>

int main(void)
{
	int i = 5; //分配了4个字节, 静态分配
	int * p = (int *)malloc(4);  //27行
	/*
		1. 要使用malloc函数,必须添加malloc.h这个头文件
		2. malloc函数只有一个形参,并且形参是整型
		3. 4表示请求系统为本程序分配4个字节
		4. malloc函数只能返回第一个字节的地址
		5. 27行分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
		6. p本身所占的内存是静态分配的,p所指向的内存是动态分配的
	*/
	*p = 5;//*p 代表的就是一个int变量,只不过*p这个整形变量的内存分配方式和i变量的分配方式不同
	free(p); //free(p)表示把p所指向的内存给释放掉  p本身的内存时静态的,不能由程序员手动释放,
			 //p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
	printf("同志们好!\n");
	
	return 0;
}

动态一维数组的构造

#include <stdio.h>
#include <malloc.h>

int main(void)
{
	int a[5]; //int占4个字节,数组总共包含有20个字节,每四个字节被当作了一个int变量来使用
	int len;
	int *pArr;
	int i;
	printf("请输入你要存放的元素的个数:");
	scanf("%d",&len);
	
	//动态的构造一维数组
	pArr = (int *)malloc(sizeof(int) * len);  //本行动态的构造了一个一维数组,该一维数组的长
	度是len,该数组的数组名是pArr,该数组的每个元素是int类型,类似于 int pArr[len];
	
    //对一维数组进行操作  如:对动态一维数组进行赋值
    for(i=0; i<len; i++)
    {
    	scanf("%d", &pArr[i]);
    }
    //对一维数组进行输出
    printf("一维数组的内容是:\n");
    for(i=0; i<len; i++)
    {
    	printf("%d\n", pArr[i]);
    }
	
	free(pArr);//释放动态分配的数组
	
	
	return 0;
}


//realloc() 函数
//void *realloc(void *ptr, size_t size) 
//尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
   char *str;
 
   /* 最初的内存分配 */
   str = (char *) malloc(15);
   strcpy(str, "runoob");
   printf("String = %s,  Address = %p\n", str, str);
 
   /* 重新分配内存 */
   str = (char *) realloc(str, 25);
   strcat(str, ".com");
   printf("String = %s,  Address = %p\n", str, str);
 
   free(str);
   
   return(0);
}

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

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

静态内存是在栈分配的

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

动态内存是在堆分配的

多级指针

#include <stdio.h >

int main(void)
{
	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;
}
非常形象的图,是变量就会有地址  (表述不一定正确,可以观看郝斌老师的视频)
int * 类型变量 p存放的是 int i变量的地址
int ** q 类型变量 存放的是 int * p变量的地址
int *** r 类型变量 存放的是 int **q 变量的地址

image-20230402200838251

跨函数使用内存

静态变量不能跨函数使用详解

//函数执行完之后,i变量的内存已经释放,不能够再访问i的空间,但是int * p变量仍能存放i之前的地址
#include <stdio.h>

void func(int ** q) //q是个指针变量,无论q是什么类型的指针变量,都只占4个字节
{
	int i = 5;
    //*q等价于p 
	//q = i; // error  因为*q = i;等价于p = i ;这样写是错误的
    *q = &i;
}

int main(void)
{
	int *p;
	func(&p);
	printf("%d\n", *p); //本语句语法没有问题,但逻辑上有问题
	
	return 0;
}

动态内存 可以跨函数使用详解

#include <stdio.h>
#include <malloc.h>
void func(int ** q)
{
	*q = (int *)malloc(sizeof(int));//sizeof(数据类型) 返回值是该数据类型所占的字节
		//等价于 p = (int *)malloc(sizeof(int));
	**q = 5;  //*p = 5
}

int main(void)
{
	int *p;
	func(&p);
	printf("%d\n", *p); 
	free(p);
	return 0;
}
//malloc分配的地址是在堆中,因此函数结束,不会对其分配的地址有影响,故可以在其他函数里面使用

杂七杂八的知识点

C语言:结构体中(.)和(->)的区别
(*ptr).b 等价于 ptr->b
两个都是二元操作符,其右操作符是成员的名称
点操作符左边的操作数是一个“结果为结构”的表达式;
箭头操作符左边的操作数是一个指向结构的指针。

注:int %d  long int %ld   char %c  float %f double %lf   十六进制%#x  八进制 %o

image-20230402213332383

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值