【C语言学习笔记】翁恺课程(4)

文章详细介绍了C语言中的指针,包括取地址运算、指针的作用、指针与数组的关系、const在指针中的应用,以及动态内存分配。通过示例代码解释了指针运算,如加1操作和比较,以及如何通过指针传递数组和修改变量值。同时强调了指针未初始化和释放内存的重要性。
摘要由CSDN通过智能技术生成

目录

9.  指针

取地址运算

指针的作用

指针最常见的错误

指针与数组

数组变量是特殊的指针

指针与const(C99 Only)

const数组

保护数组值

指针运算

指针运算

 *p++

 指针比较

0地址

指针的类型

指针的类型转换

指针用来做什么?

动态内存分配


9.  指针

取地址运算

sizeof 是一个运算符,给出某个类型或变量在内存中所占的字节数:sizeof(int), sizeof(i);

#include <stdio.h>

int main()
{
	int a = 6;
	printf("sizeof(double)=%ld\n",sizeof(double));
	printf("sizeof(int)=%ld\n",sizeof(int));
	printf("sizeof(a)=%ld\n",sizeof(a));
	return 0;
}

结果:sizeof(double)=8; sizeof(int)=4; sizeof(a)=4——int占据4个字节,一个字节8个bit,意味着这是32位变量,内存占据32bit。

运算符&:scanf("%d,&i")里面的&:
取得变量的地址,他的操作数必须是变量:int i; scanf("%d, &i")
地址的大小是否与int相同取决于编译器:int i; printf("%p, &i");
输出地址时应该用%p,而不要把地址转化为int类型后输出,不同架构值不一定同。

%p:会把值作为地址以十六进制输出,前面加0x

&不能取的地址:&必须对变量取地址,右边不是变量就不行:&(a+b)、&(a++)、&(++a)。

——  相邻变量的地址:

相差4个字节(32位架构下的一个int)先定义的在更高的地方、数字更大,C分配变量自顶向下分配。

——  数组的地址:

&a=a=a[0]=a[1]-4个字节,数组相邻位置的地址相差4.

#include <stdio.h>

int main()
{
	int a = 6;
	printf("sizeof(double)=%ld\n",sizeof(double));
	printf("sizeof(int)=%ld\n",sizeof(int));
	printf("sizeof(a)=%ld\n",sizeof(a));
	return 0;
}

int main()
{
	int i = 0;
	int p;
	//相邻变量的地址
	printf("%p\n",&i);	// 相差4个字节(32位架构下的一个int)
	printf("%p\n",&p);	// 先定义的在更高的地方、数字更大,C分配变量自顶向下分配
	
	return 0;
}

int main()
{
	int a[10];
	//数组的地址
	printf("%p\n",&a);
	printf("%p\n",a);
	printf("%p\n",a[0]);
	printf("%p\n",&a[1]);	
	
	return 0;
}

如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?. scanf("%d" , &i);
scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
什么样的类型可以接收取地址得到的那个地址?

指针:就是保存地址的变量。
int i;
int* p = &i;    //定义指针p,指向i(p中存的是i的地址)
int* p,q 和 int *p,q;  意思相同,都表示:p是一个指针指向一个int,q是一个int变量(如要表达两个指针应该位int *p,*q)。

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

作为参数的指针:void f(int *p);
在被调用的时候得到了某个变量的地址:int i = 0; f(&i);
在函数里面可以通过这个指针访问外面的这个i

#include <stdio.h>

void f(int *p );
void g(int k);

int main(void)
{
	int i=6;
	printf("&i = %p\n",&i);
	f(&i);	//main中的i的地址传给了函数f,使得f能访问main中的变量的能力
	g(i);	//main中的i的值传给了函数g
	return 0;
}

void f(int *p)
{
	printf(" p = %p\n",p);
	printf("*p = %d\n",*p);//通过p这个指针访问到了p所指的i的值
	*p = 26;				//通过指针更改了i的值,*p代表了i
}

void g(int k)
{
	printf("k = %d\n",k);//结果发现,f函数运行后,i的值发生了更改;前面函数中如传的不是地址,函数f做处理将不影响外面main的变量。
}

访问那个地址上的变量*
*是一个单目运算符,用来访问指针的值所表示的地址上的变量。可以做右值,也可以做左值:int k = *p; *p = k+1;

传入地址:为什么int i; scanf("%d",i);编译没报错?整数和地址一样大,他以为传进去的i是i的地址,实际传进去的是6,以为是地址,用这个6来进行操作,运行一定出错。

指针的作用

一、交换两个变量的值

前面学函数时,在函数中交换两个数的值,出来后值并没有交换,只是函数的变量空间中,值交换了。

#include <stdio.h>
// 交换两个变量的值
void swap(int *pa, int *pb);

int main(void)
{
	int a = 5;
	int b = 6;
	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;
}

二、 函数返会多个值,某些值就只能通过指针返回:传入的参数实际上是需要保存带回的结果的变量。

#include <stdio.h>

void minmax(int a[], int len, int *max, int *min) ;

int main(void)
{
	int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
	int min, max;
	minmax(a, sizeof(a)/sizeof(a[0] ), &min, &max);
	printf( "min=%d , max=%d\n", min, max);
	
	return 0;
}
// *min *max虽然是主函数传进去的参数,但它的作用是从函数中把结果值带出来
void minmax(int a[], int len, int *min, int *max)
{
	int i;
	*min = *max = a[0];
	for ( i=1; i<len; i++ ) {
		if ( a[i] < *min ) {
			*min = a[i];
		}
		if ( a[i] >*max ) {
			*max = a[i];
		}
	}
}

二、 函数返回运算的状态,结果通过指针返回。
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错: -1 或0 (在文件操作会看到大量的例子);
——  但是当任何数值都是有效的可能结果时,就得分开返回了。往往状态用函数的return来返回,实际的值通过指针参数来返回。如此操作容易把函数的返回结果放在if语句中。
——  后续语言C++、java采用了异常机制来解决这个问题。

两个整数做除法的函数:

#include <stdio.h>
/*
return如果除法成功,返回1;否则返回0
 */

int divide(int a, int b, int *kresult);

int main(void)
{
	int a=5;
	int b=2;
	int c;
	if ( divide(a,b,&c) ) {		// 	能除,函数返回1,ture ,if条件满足
		printf("%d/%d = %d\n", a,b,c);
	}
	return 0;
}

int divide(int a, int b, int *result){
	int ret = 1;	// ret 默认1,如果b为0,无法除,ret返回0
	if ( b == 0 ) ret = 0;
	else {
		*result = a/b;
	}
	return ret;
}

指针最常见的错误

定义了指针变量,还没指向任何变量,就开始使用指针。得先明确指针指向哪个变量,如果不,可能内存中存放指针的地方它本身有值,指向了某个变量1,此时对指针进行操作,会对变量1的值进行操作,如果不允许操作久会出错,且本身程序也有问题,没有达到本来的目的。

指针与数组

传入函数的数组成了什么?
 

sizeof(a)(a是个数组)返回的是 int* 的sizeof,而不是 int [] 的sizeof 

minmax中数组a的地址和main的地址一样,是同一个数组。

在minmax函数中改变a[0]的值,在main()函数中a[0]的值也会被改变,和指针一样。
函数参数表中int a[]数组实际上是指针,可以写成 int a[],也可写成 *a
函数中仍然可以用数组的运算符[]进行运算a[0] = 1000;

以下四种函数原型是等价的:
	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] a的地址等于a[0]的地址。
—— []运算符可以对数组做,也可以对指针做:
        p[0]<==>a[0],p[0]就相当于*p
——  *运算符可以对指针做,也可以对数组做:
        *a = 25;
——  数组变量是const的指针,所以不能被赋值。
        int a[]<==> int* const a = ...
        int b[]  -> int * const b;

指针与const(C99 Only)

指针——可以是const;值——可以是const
若:指针是const:
—— 表示一旦得到了某个变量的地址,不能再指向其他变量

	int * const q=&i;//q是const
	*q=26;			//可以改变i的值
	q++;			//ERROR,q只读,不能变

若:所指的是const
——  表示不能通过这个指针取修改那个变量(不会使得那个变量成为const)

	const int *p=&i;
	*p=26;			//ERROR!(*p)是const,不能通过p做赋值。
	i=26;			//可以  直接改i
	P=&j;			//改变p指向的地址,可以
int i;
const int*p1=&i;
int const*p2=&i;
int *const p3=&i;

加const只有两种意思:1. 地址不可改变;2.不能通过该地址来赋值
判断哪个被const的标志是:*在const前面还是后面。
转换:总可以把一个非const的值转化成const的

void f(const int*x);// 保证函数f内部不会改变指针所指的值
int a=15;
f(&a);    //可以

const int b=a;// 本来就是const的,交给f可以
f(&b);    //可以
b=a+1;    //不行!

当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。

const数组

const int a[] = {1,2,3,4,5,6,};
数组变量本身就是一个const指针,这里的const表明数组的每个单元都是const。
所以必须通过初始化赋值

保护数组值

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

指针运算

指针加1会是怎样?

#include <stdio.h>

int main (){
	
	char ac[]={0,1,2,3,4,5,6};
	char *p=ac;
	printf("p的地址是%p\n",p);
	printf("p+1的地址是%p\n",p+1);
	printf("*(p+1) = %d\n",*(p+1));
	
	int ai[]={0,1,2,3,4,5,6};
	int *q=ai;
	printf("q的地址是%p\n",q);
	printf("q+1的地址是%p\n",q+1);
	printf("*(q+1) = %d\n",*(q+1));
	
	return 0;
}

 在地址上加一个sizeof基础类型的大小。

给一个指针加1表示要让指针指向下一个变量
        int a[l0];
        int*p = a;
        *(p+n)—> a[n]
如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义.
注意是*(p+1)而不是*p+1,因为*号是单目运算符,优先级高。

指针运算

这些算术运算可以对指针做:
        给指针加、减一个整数(+,+=,-,-=)
        递增递减(++/—)
        两个指针相减:得到

的是地址差/sizeof基础类型——中间能放几个这样类型的东西。

 *p++

——  取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
——  *的优先级虽然高,但是没有++高
——  常用于数组类的连续空间操作
——  在某些CPU上,这可以直接被翻译成一条汇编指令

#include<stdio.h>
// 遍历数组

int main(void)
{
	char ac[] = {0,1,2,3,4,5,6,7,8,9,-1};
	char *p = &ac[0];
	int i;
	
	for ( i=0; i<sizeof(ac)/sizeof(ac [0] ); i++ ) {
		printf( "%d\n", ac[i] );
	}
	
	//for ( p=ac; *p!=-1 ; ) {
	while ( *p != -1 ) {
		printf ("%d\n",*p++);
	}
	
	return 0;
}

 指针比较

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

0地址

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

指针的类型

无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
但是指向不同类型的指针是不能直接互相赋值的
这是为了避免用错指针

指针的类型转换

void*表示不知道指向什么东西的指针
        计算时与char*相同(但不相通)
指针也可以转换类型
        int*p = &i; void*q = (void*)p;
这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
        我不再当你是int啦,我认为你就是个void!

#include <stdio.h>

//int main (){
//	
//	char ac[]={0,1,2,3,4,5,6};
//	char *p = ac[0];
//	char *p1 = ac[5];
//	printf("p的地址是%p\n",p);
//	printf("p+1的地址是%p\n",p+1);
//	printf("*(p+1) = %d\n",*(p+1));
//	printf("*(p1-p) = %d\n",p1-p);
//	
//	int ai[]={0,1,2,3,4,5,6};
//	int *q = ai[0];
//	int *q1 = ai[6];
//	printf("q的地址是%p\n",q);
//	printf("q+1的地址是%p\n",q+1);
//	printf("*(q+1) = %d\n",*(q+1));
//	printf("*(q1-q) = %d\n", q1-q);
//	
//	return 0;
//}


int main(void)
{
	char ac[] = {0,1,2,3,4,5,6,7,8,9,-1};
	char *p = &ac[0];
	int i;
	for ( i=0; i<sizeof(ac)/sizeof(ac [0] ); i++ ) {
		printf( "%d\n", ac[i] );
	}
	
	//for ( p=ac; *p!=-1 ; ) {
	while ( *p != -1 ) {
		printf ("%d\n",*p++);
	}
	int ai[] = {0,1,2,3,4,5,6,7,8,9,};
	int *q = ai;
	q = p;	//[警告] assignment to 'int *' from incompatible pointer type 'char *' [-Wincompatible-pointer-types]
	return 0;
}

指针用来做什么?

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

动态内存分配

输入数据

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

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int number;
	int* a;
	int i;
	printf("输入数量:");
	scanf("%d",&number);
	//int a[number];   //C99可以这么干
	a = (int*)malloc(number*sizeof(int));//malloc要的是字节为单位,malloc返回void*,要转换成int*
	//后面都是拿a当数组来用
	// 键入产生数组
	for(i=0;i<number;i++){
		scanf("%d",&a[i]);
	}
	//逆序输出数组
	for(i=number-1;i>=0;i--){
		printf("%d",a[i]);
	}
	free(a);//释放空间
	return 0;
}

malloc
需要头文件:#include <stdlib.h>
void* malloc(size_t size);
- 向malloc申请的空间大小是以字节为单位的
- 返回结果是void* 需要转换为自己需要的类型;
 - (int*)malloc(number*sizeof(int));

没空间了?
如果申请失败,则返回0,或者叫做NULL。
你的系统能给你多大的空间 ?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	void *p;
	int cnt = 0;
	while( (p = malloc(100*1024*1024)) ){
		cnt++;
	}
	printf("分配了%d00MB的空间\n",cnt);
	
	free(p);
	return 0;
}

free()
把申请来的空间还给系统
申请过的空间,最终都应该要还,只能还申请来的空间的的首地址。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	void *p;
	int cnt = 0;
	p=malloc ( 100*1024*1024);
	p++;
	free(p);//p++运算后free p
	
	return 0;
}

 释放的不是申请来的,异常终止

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int i;
	void *p;
	int cnt = 0;
//	p=malloc ( 100*1024*1024);
//	p++;
	p = &i;
	free(p);//p不是malloc来的
	
	return 0;
}

  释放的不是申请来的,异常终止

free(NULL)  —— 没问题:0不可能是malloc得到的有效地址,

养成良好习惯:定义指针的时候就先初始为0
void *p = 0; 之后 再free(p), 不管有没使用malloc,free()都没有问题。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int i;
	void *p;
	int cnt = 0;
//	p=malloc ( 100*1024*1024);
//	p++;
	free(p);//p不是malloc来的
	
	return 0;
}

如此,free(p) 没有问题。

free(1),不可以,1这个地址不能被free。

常见问题
申请了没有free——长时间运行内存逐渐下降:新手:忘了;老手:找不到合适的时机。
free过了再free
地址变过了,直接free

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值