IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 指针——C语言程序设计(七) -> 正文阅读

[C++知识库]指针——C语言程序设计(七)

C语言程序设计(七)

指针

取地址运算

运算符 &

  • scanf("%d",i); 里的&
  • 获得变量的地址,它的操作数必须是变量
  • int i; printf(“%x”, &i);
  • 地址的大小是否与int相同取决于编译器
  • int i; printf(“%p”,&i) //取地址
#include <stdio.h>
int main()
{
	int i = 0;
	int p;
	p = (int)&i; //强制类型转换, 将i的地址强制转换成int型变量 
	printf("0x%x\n", p); //%x输出十六进制 	
	printf("%p\n",&i); //%p输出地址 
	printf("0x%x\n", p); 
	return 0; 
}
  1. 格式控制符%p中的p是pointer(指针)的缩写。指针的值是语言实现(编译程序)相关的,但几乎所有实现中,指针的值都是一个表示地址空间中某个存储器单元的整数。printf函数族中对于%p一般以十六进制整数方式输出指针的值,附加前缀0x

  2. %p是打印地址的, %x是以十六进制形式打印, 完全不同在32位下可能一样,但是在64位下结果会不一样, 所以打印指针老老实实用%p

&不能取的地址

  • &不能对没有地址的东西取地址(即它右边一定是明确的变量, 其他的取不了地址)
  • &(a+b)? 不能取
  • &(a++)? 不能取
  • &(++a)? 不能取

试试这些&

  • 变量的地址 : printf(“%p”,&i)

  • 相邻的变量的地址 : 存储在连续的内存空间中

  • &的结果的sizeof

#include <stdio.h>
int main(void)
{
	int i = 0;
	int p;
	printf("0x%x\n", &i); //%x输出十六进制 
	printf("%p\n", &i);
	printf("%lu\n", sizeof(int));
	printf("%lu\n", sizeof(&i)); 
	return 0; 
}

输出结果:

0x62fe1c
000000000062FE1C
4
8
  • 数组的地址

  • 数组单元的地址

  • 相邻的数组单元的地址

#include <stdio.h>
int main(void)
{
	int a[10];
	printf("%p\n", &a); //数组单元的地址
	printf("%p\n", a);  
	printf("%p\n", &a[0]);//数组单元的地址
	printf("%p\n", &a[1]);//相邻的数组单元的地址
	return 0; 
}

输出:

000000000062FDF0
000000000062FDF0
000000000062FDF0
000000000062FDF4
  • 相邻的数组的内存地址相差4; 一个int型所占的空间(4字节)

scanf

  • 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? 答案是:可以的
  • scanf(“%d”, &i);
  • scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量? 这时候就要用到指针.

指针

  • 指针类型变量就是保存地址的变量
    • int i;
    • int* p = &i; //p为指针型变量, 存放int i的内存地址,就是指向i. 其中*可以靠近int或者靠近p都是一样的
    • int* p,q; //和int *p,q;表达的意思一样,都是p是指针变量,而q不是.
    • int *p,q; //和int* p,q;表达的意思一样,都是p是指针变量,而q不是.int *p,q;
    • int *p, *q; //这样才表示p和q都是指针型变量.

指针变量

  • 变量的值是内存的地址
  • 普通变量的值是实际的值
  • 指针变量的值是具有实际值的变量的地址

请添加图片描述

作为参数的指针

  • void f(int *p);
  • 在被调用的时候得到了某个变量的地址:
    • int i=0; f(&i);
  • 在函数里面可以通过这个指针访问外面的这个i
#include <stdio.h>
void f(int *p);
int main(void)
{
	int i = 6;
	printf("&i=%p\n", &i);//输出i的地址
	f(&i);//将i的地址传入f函数
	
	return 0;
}

void f(int *p)
{
	printf(" p=%p\n", p); //输出p的值
}

输出结果:

&i=000000000062FE1C
 p=000000000062FE1C

都是一样的

请添加图片描述

访问那个地址上的变量*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
    • 比如:int *p = &a; //是从右往左结合的
  • 可以做右值也可以做左值
    • int k = *p;
    • *p = k+1;
#include <stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
	int i = 6;
	printf("&i=%p\n", &i);
	f(&i);
	g(i);
	return 0;
}

void f(int *p)
{
	printf(" p=%p\n", p); //p的值就是i的内存地址
	printf("*p=%p\n", *p); //*p直接访问i变量的值   或者也可以: printf("*p=%d\n", *p);
    *p = 26;// 直接修改变量i上的值
}

void g(int k)
{
	printf(" k=%d\n",k);  //只是简单的将i的值赋给k;
 } 

输出结果:

&i=000000000062FE1C
 p=000000000062FE1C
*p=0000000000000006
 k=26

左值之所以叫左值

  • 是因为出现在赋值号左边的不是变量,而是,是表达式计算的结果
    • a[0] = 2;
    • *p = 3; //必须定义了指针变量,而且指向另一个变量,才能进行赋值
    • a = b; //是将变量b的值而不是变量b赋给变量a
  • 是特殊的值,所以叫做左值

指针的运算符&*

  • 互相反作用

  • *&yptr -> * (&yptr) -> * (yptr的地址)-> 得到那个地址上的变量 -> yptr

    • 比如:a是b的指针变量;a == &b; *a == b;*&b==b;
  • &*yptr -> &(*yptr) -> &(y) ->得到y的地址,也就是yptr -> yptr

  • 比如:a是b的指针变量;*a == b; &b ==a;&*a == a;

传入地址
  • 为什么int i; scanf(“%d”, i);编译没有报错?

答:原因是int型和指针型在32位的操作系统上他们的长度是一致的,编译器认为它的值是内存地址,所以可以编译过关;但运行一定出错,因为它的值不是它的内存地址,系统会将你要输入的值写到其他地方(就是把int i的值当做内存地址进行写入),而你的i变量上就没有你输入的值,从而报错。

指针运用场景(一)

  • 交换两个变量
void swap(int *pa, int *pb)
{
    int t = *pa;
    *pa = *pb;
    *pb =t;
}
  • 交换a,b的值
#include <stdio.h>
void swap(int *pa, int *pb);
int main()
{
	int a = 5;
	int b = 6;
	printf("交换前: a=%d, b=%d\n", a,b);
	swap(&a, &b);
	printf("交换后: a=%d, b=%d\n", a,b);
	
	return 0;
}

void swap(int *pa, int *pb)
{
    int t = *pa;
    *pa = *pb;
    *pb =t;
}

输出结果:

交换前: a=5, b=6
交换后: a=6, b=5
  • 误区:

  • void swap(int a, int b)
    {
        int t = a;
        a = b;
        b =t;
    }//这样的交换并不能达到交换a,b两个值的目的
    
  • 这样的交换并不能达到交换a,b两个值的目的,因为你传进去的只是变量的值而不是变量, 而函数里面它又重新开辟有一个新的空间,即开辟新的a,b两个内存空间,然后里面的传值只是传到新的地址空间,并没有改变外面原来的a,b变量的值。*而即使你的外面的变量名和函数里面的变量名相同也是一样达不到交换的目的,他们只是相同变量名存放在不同的内存地址。*因此必须用到指针变量来进行交换,本质就是把要交换的变量的值进行修改,就是要找到那个变量的地址,在存放的地址上进行修改值。

指针应用场景(二)

  • 函数返回多个值,某些值就只能通过指针返回
  • 传入的参数实际上是需要保存带回的结果的变量
#include <stdio.h>
void minmax(int a[], int length, int *min, int *max);
int main(void)
{
	int a[] = {1, 2, 5, 4, 7, 1, 6, 9, 8};
	int min, max;
	int length = sizeof(a)/sizeof(a[0]);
	minmax(a, length, &min, &max);//将min和max的变量地址给函数 
	printf("min = %d, max = %d\n", min, max);
	return 0;
}

void minmax(int a[], int length, int *min, int *max)//*min和*max都是指针变量;是用来访问所指内存地址上的变量值,并且可以修改该值的 
{
    int i;
    *min = *max = a[0];
    for(i=1; i<length; i++){
    	if(*min>a[i]){
    		*min = a[i];//当*min大于a[1],将a[1]小值赋给*min 
		}
		if(*max<a[i]){
			*max = a[i];
		}//这样就将值带回到原来的变量地址上
	}
}

输出:

min = 1, max = 9
  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
    • -1或0(在文件操作会看到大量的例子)
#include <stdio.h>
int divide(int a, int b, int *c);
int main()
{
	int a = 5;
	int b = 2;
	int c;
	if (divide(a, b, &c)){ //当divide函数返回1则说明条件成立,返回0则条件不成立
		printf("%d/%d=%d\n", a, b, c);
	}

 } 
 
 int divide(int a, int b, int *c)
 {
 	int ret;
 	if(b==0){
 		ret = 0;
	 }else{
	 	*c = a/b;
	 	ret = 1;
	 }
	 return ret;
 }
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
  • 后续的语言(C++, Java)采用了异常机制来解决这个问题

指针最常见的错误

  • 定义了指针变量,还没有指向任何变量,就开始使用指针

请添加图片描述

#include <stdio.h>
int main()
{
	int *a; //定义指针变量a 
	int b = 1;//定义变量b 
	printf(" b=%d\n",b);
	a = &b; //将指针变量a指向变量b 
	*a =2;  //修改b的值 
	printf(" a=%d\n",a); 
	printf("*a=%d\n",*a);
	printf(" b=%d\n",b);
	printf("&b=%d\n",&b);
	b = 3;  //这样赋值也可以改变变量的值
	printf(" b=%d\n",b);
	printf("&b=%d\n",&b);
	 
}

输出结果:

 b=1
 a=6487572
*a=2
 b=2
&b=6487572
 b=3
&b=6487572

即必须将指针变量指向某一个变量的时候才能对其进行操作.

指针与数组

  • 传入函数的数组成了什么?
    • 函数参数表中的数组实际上是指针
    • sizeof(a) == sizeof(int*) //a的长度等于int*的长度;在汇编代码中a相当于数组a[]的头指针.
    • 但是可以用数组的运算符[]进行运算
#include <stdio.h>
void minmax(int a[], int length, int *min, int *max);
int main(void)
{
	int a[] = {1, 2, 5, 4, 7, 1, 6, 9, 8};
	int min, max;
	printf("main minmax sizeof(a)=%lu\n", sizeof(a)); //输出数组a的大小
	printf("main a=%p\n", a); //输出a的值
	int length = sizeof(a)/sizeof(a[0]);
	minmax(a, length, &min, &max);
	printf("min = %d, max = %d\n", min, max);
	printf("a[0]=%d\n", a[0]);
	return 0;
}

void minmax(int a[], int length, int *min, int *max)
{
	int i;
    printf("minmax sizeof(a)=%lu\n", sizeof(a)); //输出数组a的大小
    
	a[0] = 100;  //a[0]相当于头指针,也就是一种指针 
	*min = *max = a[0];
    for(i=1; i<length; i++){
    	if(*min>a[i]){
    		*min = a[i];//当*min大于a[1],将a[1]小值赋给*min 
		}
		if(*max<a[i]){
			*max = a[i];
		}
	}
}

输出结果:

main minmax sizeof(a)=36
main a=000000000062FDF0
minmax sizeof(a)=8
min = 1, max = 100
a[0]=100
  • 如果将void minmax(int a[], int length, int *min, int *max)改成void minmax(int *a, int length, int *min, int *max) ,运行结果一模一样,这说明a[]就是一个指针变量. 所以在函数参数中不能传入a[n]格式,而是直接将a[]数组以a传入,函数中的参数接收不了整个数组,但可以接收指针,而数组指针更可以。

请添加图片描述

#include <stdio.h>
void p(int *a);
int main(void)
{

	int a = 12;  
	printf("main a=%d\n", a); //输出a的值 
	printf("main a=%p\n", a); //输出a的内存地址 
	p(&a);
	printf("main a=%d\n", a); //输出a的值 
	return 0;
}

void p(int *b)
{

	b[0] = 100;  
	b[2] = 2;
	printf("b[0]=%d\n", b[0]);
	printf("b[2]=%d\n", b[2]);
	printf("b=%d\n", b); //这里的a是指函数中a的值,也就是 
}

输出结果:

main a=12
main a=000000000000000C
b[0]=100
b[2]=2
b=6487580
main a=100

数组参数

  • 以下四种函数原型是等价的:
    • int sum(int *ar, int n);
    • int sum(int *, int);
    • int sum(int ar[], int n);
    • int sum(int [], int);

数组变量是特殊的指针

  • 数组变量本身表达地址,所以int a[10]; int*p=a;// 无需用&取地址
  • 但是数组的单元表达的是变量,需要用&取地址
    • a == &a[0] //即数组的头指针也就是数组的第一个元素的内存地址
  • []运算符可以对数组做,也可以对指针做:
    • p[0] <==> a[0]
  • *运算符可以对指针做,也可以对数组做:
    • *a = 25;
  • 数组变量是const(常量)的指针,所以两个数组间不能被赋值
    • int a[] <==> int * const a= ….

附加知识点*

指针与const(只适用于C99)

指针是const

  • 表示一旦得到了某个变量的地址,不能再指向其他变量
    • int * const q = &i;// q 是 const, 即q的值不能被改变,也就是i的地址
    • *q = 26;// OK
    • q++; // ERROR 是错误的; 不能将q的值进行运算,因为q是const

请添加图片描述

所指是const(指针所指的变量是const)

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
const int *p = &i;  //将变量i的地址赋给*p
*p = 26; // ERROR!不能;  (*p) 是 const; 不能通过p进行修改变量i的值,因为`(*p)是const, 而i不是const,可以进行i++运算
i = 26;  //OK是可以进行的
p = &j; //OK是可以进行的

这些是啥意思?

  • int i;

  • const int* p1 = &i; //p1指针所指的东西不能被修改

  • int const* p2 = &i; //p2指针所指的东西不能被修改

  • int *const p3 = &i; //p3指针不能被修改

    判断哪个被const了的标志是const在*的前面还是后面

const在*前面是指针所指的东西不能被修改; const在*后面是指针不能被修改

转换

  • 总是可以把一个非const的值转换成const的

    void f(const int* x);
    
    int a = 15; f(&a); // ok可以
    
    const int b = a; f(&b); // ok可以
    
    b = a + 1; f(&b); // Error!不能, 因为 a+1不是const
    
  • 当要传递的参数的类型比地址大的时候,这是常用的手段既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改 (比如传入一个十分大的数组进函数, 实际是传入一个指针进去)

const数组

  • const int a[] = {1,2,3,4,5,6,};//这是一个const数组;必须通过这种方式进行初始化,否则写出来后就不能进行赋值了.
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
    • int sum(const int a[], int length); //这样传进函数内就不会被修改了

指针运算

指针是可计算的

  • 给一个指针加1表示要让指针指向下一个变量

    int a[10];
    int *p = a;
    *(p+1)-> a[1]
    
  • 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

  • 这些算术运算可以对指针做:

    • 给指针加、减一个整数(+, +=, -, -=)
    #include <stdio.h>
    int main(void)
    {
    	char ac[] = {0, 1, 2, 3, 4, 5, };
    	char *p = ac; //数组的头指针赋给指针p 
    	printf("p     =%p\n", p); //指针p的值 
    	printf("p+1   =%p\n", p+1); //指针p+1的值 
    	printf("*(p+1)=%d\n",*(p+1)); //指针p+1所指的那个变量的值 
    	int ai[] = {0, 1, 2, 3, 4, 5, };
    	int *q = ai; //数组的头指针赋给指针q 
    	printf("q     =%p\n",q);  //q指针q的值 
    	printf("q+1   =%p\n",q+1);  //指针q+1的值 
    	printf("*(q+1)=%d\n",*(q+1));//指针q+1所指的那个变量的值 
    	return 0;		
     } 
    
    

    输出结果:

    p     =000000000062FE00
    p+1   =000000000062FE01
    *(p+1)=1
    q     =000000000062FDE0
    q+1   =000000000062FDE4
    *(q+1)=1
    

    所以sizeof(char) = 1; sizeof(int) = 4;

    • 递增递减(++/–)
    • 两个指针相减
    #include <stdio.h>
    int main(void)
    {
    	char ac[] = {0, 1, 2, 3, 4, 5, };//char型数组
    	char *p = &ac[0]; //第1个元素的内存地址 
    	char *p1 = &ac[5]; //第6个元素的内存地址 
    	printf("p     =%p\n", p); //指针p的值 
    	printf("p+1   =%p\n", p+1); //指针p+1的值 
    	printf("p1 - p=%d\n",p1-p); //指针p+1与指针p相减 
    	int ai[] = {0, 1, 2, 3, 4, 5, };//int型数组
    	int *q = ai; //数组的头指针赋给指针q ; ai也相当于&ai[0] ; 即 ai==&ai[0]  
    	int *q1 = &ai[5]; 
    	printf("q     =%p\n",q);  //q指针q的值 
    	printf("q+1   =%p\n",q+1);  //指针q+1的值 
    	printf("q1 - q=%d\n",q1-q);//指针q+1与指针q相减 
    	return 0;		
     } 
    

    输出结果:

    p     =000000000062FDF0
    p+1   =000000000062FDF1
    p1 - p=5
    q     =000000000062FDD0
    q+1   =000000000062FDD4
    q1 - q=5
    
  • 可以看出指针的加减并不是以内存地址的字节来输出运算结果,而是通过加减所得的字节长除以类型变量所占的字节长得出的运算结果, 并不是简单的加减一。

  • 相同内容的数组,不同类型所占的空间不一样;比如int型数组和char型数组,即使元素个数相等,元素值相同,但数组所占空间大小不一样。

  • 注意:指针可以做加减法, 但是没有乘除法。

*p++

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令
#include <stdio.h>
int main(void)
{
	char ac[] = {0, 1, 2, 3, 4, 5, };
	char *p = &ac[0]; //第1个元素的内存地址 
	int i;
	for(i=0; i<sizeof(ac)/sizeof(ac[0]); i++){
		printf("%d ",ac[i]);
	}//第一种方法遍历数组 
	
 	printf("\n");
	
	int ai[] = {0, 1, 2, 3, 4, 5, -1};//用-1作为结束的判断条件 
	int *q = ai; //数组的头指针赋给指针q ; ai也相当于&ai[0] ; 即 ai==&ai[0]  

	//第二种方法遍历数组 
    {
		for(q=ai;*q!= -1;  ){
			printf("%d ",*q++);//它的操作使指针值发生变化
		}
		printf("\n");	
	}
	


	//或者用while循环 
	{
		int *q = ai; //在块内得重新定义指针变量 
		while(*q!= -1){
			printf("%d ",*q++);
		}
	}
	
	return 0;		
 } 

输出结果:

0 1 2 3 4 5
0 1 2 3 4 5
0 1 2 3 4 5

指针比较

  • <, <=, ==, >, >=, != 都可以对指针做
  • 比较它们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以你的指针不应该具有0值
  • 因此可以用0地址来表示特殊的事情:
    • 返回的指针是?无效的
    • 指针没有被真正初始化(先初始化为0)
  • NULL(必须全是大写)是一个预定定义的符号,表示0地址
    • 有的编译器不愿意你用0来表示0地址

指针的类型

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址

  • 但是指向不同类型的指针是不能直接互相赋值的

    • 这是为了避免用错指针
  • 可以使用强制类型转换*(但初学者不建议用)*

指针的类型转换

  • void* 表示不知道指向什么东西的指针

    • 计算时与char*相同(但不相通)
  • 指针也可以转换类型

    • int *p = &i; void *q = (void*)p;
  • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量

    • 我不再当你是int啦,我认为你就是个void!

用指针来做什么

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
    • 需要用函数来修改不止一个变量
  • 动态申请的内存…

动态内存分配

输入数据

  • 如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
  • C99可以用变量做数组定义的大小,C99之前呢?必须用动态内存分配
  • malloc函数
    • int *a = (int*)malloc(n*sizeof(int));

malloc

#include <stdlib.h>
void* malloc(size_t size);
  • 在使用malloc前, 需要加入头文件#include <stdlib.h>
  • malloc申请的空间的大小是以字节为单位的
  • 返回的结果是void*,需要类型转换为自己需要的类型*
    • *(int*)malloc(n*sizeof(int))
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int number;
	int* a;
	int i; 
	printf("输入数字的数量:");
	scanf("%d", &number);
	
	a = (int*)malloc(number*sizeof(int)); //malloc分配的空间是以字节为单位的,而不是变量个数. 
	//malloc返回的结果是一个void*类型, 所以要强制类型转换
	//动态内存分配, malloc分配内存的首地址给a,然后从首地址起,到分配的内存大小所需字节长度的连续空间. 
	
	printf("请依次输入数字,以空格隔开:");
	for (i=0; i<number; i++){
		scanf("%d",&a[i]);
	} 
	
	for ( i=number-1; i>=0; i--){
		printf("%d ",a[i]); 
	}
	
	free(a); //最后使用完要释放malloc申请的内存空间 
	return 0; 
} 

输出结果:

输入数字的数量:5
请依次输入数字,以空格隔开:1 2 3 4 5
5 4 3 2 1

没空间了?

  • 如果申请失败则返回0,或者叫做NULL
  • 你的系统能给你多大的空间?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	void* p;
	int cnt = 0;
	while ( (p=malloc(100*1024*1024)) ) { //1024个字节*1024 = 1Mb;  1字节 = 8位; 1024个字节 = 1kb; 1024kb = 1Mb 
		cnt++; //相等于每次累加100Mb大小的空间 
	}// (p=malloc(100*1024*1024)) 这个表达式作为while的条件 ,如果p被分配的地址不是0,则条件成立 
	printf("分配了%d00MB的空间\n", cnt);//计算机能分配的最大内存空间 
	
	free(p); 
	return 0; 
} 

输出:

分配了33600MB的空间

每个计算机能分配的最大空间都不一定相同

free()

  • 把申请得来的空间还给“系统”
  • 申请过的空间,最终都应该要还
    • 出来混的,迟早都是要还的 所以一定要释放申请的内存空间
  • 只能还申请来的空间的首地址(原地址) , 还回去的首地址只能是之前申请的首地址
    • free(0) 和 free(NULL)一样效果
	void* p = 0;
	p=malloc(100*1024*1024);
	p++;
	free(p);  //程序会终止进行,因为它释放的空间不是之前分配空间的首地址,所以有P++之后不能这样释放空间 
	return 0;  
  • ? void* p = 0 ; //定义指针的时候好习惯是先初始化它为0;
  • 其次free(NULL);是指啥操作也不做,单纯是一种习惯

常见问题

  • 申请了没free—>长时间运行内存逐渐下降 ,又称内存垃圾或内存漏洞,程序申请的内存总是有限的

    • 新手:忘了
    • ?手:找不到合适的free的时机
  • 已经free过了再free(也会崩溃)

  • 地址变过了,直接去free ; (比如:p++的例子)

函数间传递指针

好的模式

  • 如果程序中要用到动态分配的内存,并且会在函数之间传递,不要让函数申请内存后返回给调用者
  • 因为十有八九调用者会忘了free,或找不到合适的时机来free
  • 好的模式是让调用者自己申请,传地址进函数,函数再返回这个地址出来
#include <stdio.h>
#include <stdlib.h> 
int* init(int a[], int length);
int* print(int a[], int length);

int main()
{
	const int MAX_SIZE = 1000;
	int size;
	do{
		printf("输入元素的数量(0,1000):");
		scanf("%d",&size);
	}while(size<=0 && size>MAX_SIZE);
	int* a = (int*)malloc( size*sizeof(int) ) ;//分配size个int整型的字节长度空间 
	print(init(a,size),size);//先调用函数init() 将函数的返回值再传入print()函数 
	free(a);//最后使用完后要释放内存空间	
}

int* init(int a[], int length)//逐个读入元素数据,组成数组 
{
	int i;
	for (i=0; i<length; i++){
		a[i] = i;
	}
	return a;//执行完后返回数组的首地址;即返回指针
} 

int* print(int a[], int length) //遍历输出数组的元素值 
{
	int i;
	for(i=0; i<length; i++){
		printf("%d\t", a[i]);
	}
	printf("\n");
	return a;//执行完后返回数组的首地址;即返回指针 
}

输出结果:

输入元素的数量(0,1000):20
0       1       2       3       4       5       6       7       8       9       10      11      12      13      14
        15      16      17      18      19

函数返回指针?

  • 返回指针没问题,关键是谁的地址?
    • 本地变量(也叫局部变量; 包括参数)?函数离开后这些变量就不存在了,指针所指的是不能用的内存
    • 传入的指针?没问题
    • 动态申请的内存?没问题
    • 全局变量—>它的作用域默认是整个程序

函数返回数组?

  • 如果一个函数的返回类型是数组,那么它实际返回的也是数组的地址

  • 如果这个数组是这个函数的本地变量,那么回到调用函数那?里,这个数组就不存在了

  • 所以只能返回

    • 传入的参数:实际就是在调用者那里
    • 全局变量或动态分配的内存
  • 和返回指针是一样的(函数返回数组的本质就是函数返回指针)

注意 : 除非函数的作用是分配空间, 否则不要在函数中malloc然后传出去用


局部变量和全局变量; 形参和实参

拓展:

局部变量:定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。

  1. 在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数虽然是主函数,但也是一个函数,与其它函数地位平等。

  2. 形参变量、在函数体内定义的变量都是局部变量。实参给形参传值的过程也就是给局部变量赋值的过程。

  3. ==局部变量可以与全局变量同样命名。==也可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。但是优先级是局部变量优先

  4. 在语句块中也可定义变量,它的作用域只限于当前语句块。

全局变量 :在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。

举个栗子:

#include <stdio.h>

int n = 0;  //全局变量
void func1(){
    int n = 1;  //局部变量
    printf("func1 n: %d\n", n);//函数func1的n值的输出 
}
void func2(int n){
    printf("func2 n: %d\n", n);//函数func2的n值的输出
}
void func3(){
    printf("func3 n: %d\n", n);//函数func3的n值的输出
}
int main(){
    int n = 2;  //局部变量
    func1();
    func2(n);  //用局部变量n = 2 做参数传入 ,若有局部变量和全局变量的命名相同,优先级是局部变量使用优先 
    func3();
    //代码块由{}包围
    {
        int n = 3;  //局部变量
        printf("block n: %d\n", n);//块内的n值的输出 
    }
    printf("main  n: %d\n", n);//主函数main的n值的输出
    return 0;
}

输出:

func1 n: 1
func2 n: 2
func3 n: 0
block n: 3
main  n: 2

形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。所谓作用域(Scope),就是变量的有效范围。

C语言函数的参数会出现在两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的。

形参(形式参数):

在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参

实参(实际参数):

函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。


  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-28 21:56:42  更:2021-08-28 21:56:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 20:26:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计