C语言入门-指针

终于到了精髓的地方了,这确实有点懵,总感觉这太麻烦了,而且写着也不爽,还是怀念py或者java,但也没办法,还是要继续学下去。

一、运算符&

  1. scanf("%d" , &i); 里的&
  2. 获取变量的地址,它的操作数必须是变量
  3. 地址的大小是否与int相同取决于编译器
#include <stdio.h>
int main(void)
{
	int i = 0;
	printf("0x%x\n", &i);
	// 0x62fe4c
	return 0;
}

&不能取的地址

&不能对没有地址的东西取地址

  1. &(a+b) ?
  2. &(a++) ?

二、指针

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

指针

就是保存地址的变量 , *p

// p是一个指针,现在把i的地址交给了p
int i;
int* p = &i;

// 下面两种形式一样,p是一个指针,而q是一个普通的int变量
int* p , q;
int *p , q;

指针变量

  1. 变量的值是内存的地址
  2. 普通变量的值是实际的值
  3. 指针变量的值是具有实际值的变量的地址

作为参数的指针

  1. void f(int *p);
  2. 在被调用的时候得到了某个变量的地址
  3. int i = 0;f(&i);
  4. 在函数里面可以通过这个指针访问到外面的的这个i
#include <stdio.h>

void f(int *p);

int main(void)
{
	int i = 6;
	printf("&i=%p\n", &i);
	f(&i);

	return 0;
}

void f(int *p)
{
	printf(" p=%p\n", p);
}

// 可以看到这里获取的地址是相同的
// &i=000000000062FE4C
//  p=000000000062FE4C

访问那个地址上的变量*

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

*左值之所以叫左值

  1. 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果
  2. a[0] = 2;
  3. *p = 3;
  4. 是特殊的值,所以叫左值
#include <stdio.h>

// 声明两个函数
void f(int *p);
void g(int k);


int main(void)
{
	int i = 6;
	printf("&i=%p\n", &i);
	f(&i);
	
	// 此时i的值已经发生了变化
	g(i);

	return 0;
}


// 传入的是地址
void f(int *p)
{
	printf(" p=%p\n", p);
	printf("*p=%d\n", *p);
	*p = 66;
}

// 传入的普通int
void g(int k){
	printf("k=%d\n", k);
}


// &i=000000000062FE4C
//  p=000000000062FE4C
// *p=6
// k=66

三、指针的使用

指针应用场景一

交换两个变量的值

#include <stdio.h>

void swap(int *pa , int *pb);

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

指针应用场景二

  1. 函数返回多个值,某些值就只能通过指针返回
  2. 传入的参数实际上是需要保存带回的结果的变量
#include <stdio.h>

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

int main(void)
{
	int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,15,34,35,66,};
	int min , max;
	minmax(a , sizeof(a)/sizeof(a[0]) , &min , &max);
	printf("min = %d , max = %d \n", min , max);

	return 0;
}


void minmax(int a[] , int len , int *min , int *max)
{
	int i;
	*min = *max = a[0];
	for (i = 0; i < len; i++)
	{
		if (a[i] > *max)
		{
			*max = a[i];
		}
		if (a[i] < *min)
		{
			*min = a[i];
		}
	}
}

指针应用场景二b

  1. 函数返回运算的状态,结果通过指针返回
  2. 常用的套路是让函数返回特殊的不属于有效范围内的值表示出错
    • -1 或 0
  3. 但是当任何数值都是有效的可能结果时,就得分开返回了
#include <stdio.h>

// 如果成功就返回1,否则就是0
int divide(int a , int b , int *result);

int main(void)
{
	int a = 5;
	int b = 2;
	int c;
	if (divide(a,b,&c))
	{
		printf("%d/%d = %d\n", a, b, c);
	}

	return 0;
}

int divide(int a , int b , int *result)
{
	int ret = 1;
	if ( b== 0)
	{
		ret = 0;
	}else{
		*result = a/b;
	}

	return ret;
}

指针常见的错误

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

四、指针与数组

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

  1. 函数参数表中的数组实际上是指针
  2. sizeof(a) == sizeof(int*)
  3. 但是可以用数组的运算符[]进行运算

下面的四种函数原型是等价的

int sum(int *ar , int n);
int sum(int* , int);
int sum(int ar[] , int n);
int sum(int[] , int);

数组变量是特殊的指针

数组变量本身表达地址,所以

  1. int a[10]; int*p = a; // 无需用&取地址
  2. 但是数组的单元表达的是变量,需要用&取地址
  3. a == &a[0]

[]运算符可以对数组做,也可以对指针做

  1. p[0] <===> a[0]

*运算符可以对指针做,也可以对数组做

  1. *a = 25

数组变量是const的指针,所以不能被赋值

五、指针与const

  1. 表示一旦得到了某个变量的地址,不能再指向其他变量
	// q内写的地址不能被改变
	int *const q = &i;
	*q = 26; // ok
	q++;   // error
  1. 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
	const int *p = &i;
	*p = 26; // error  (*p是不能变的)
	i = 26; // ok
	p = &j; // ok
  1. 看懂下面的意思?
	const int * p1 = &i;
	int const * p2 = &i;

	int *const  p3 = &i;

判断那个被const的标志是const在*的前面还是后面
前面两个*p不能被修改,就像第二种情况

const数组

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

保护数组值

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

六、指针运算

1+1 = 2 ?那么指针加1等于什么呢

#include <stdio.h>

int main(void)
{
	char ac[] = {0,1,2,3,4,5,6,7,8,9,};
	char *p = ac;

	printf("p =%p\n", p);
	printf("p + 1 =%p\n\n", p+1);
	// p =000000000062FE30
	// p + 1 =000000000062FE31
	// 相差了1
    
	int ai[] = {0,1,2,3,4,5,6,7,8,9,};
	int *q = ai;

	printf("q =%p\n", q);
	printf("q + 1 =%p\n\n", q+1);
	//q =000000000062FE00
	//q + 1 =000000000062FE04
    // 相差了4

	return 0;
}

我们不难发现,在char中,相差了1,在int中相差了4,这是怎么回事?

sizeof(char) = 1 , sizeof(int) = 4

对指针做一个加1的动作,意味着要把它移动到下一个单元去

int a[10];
int *p = a;
*(p+1) --> a[1]

如果指针不是指向一片连续分配的空间,如数组,那么这运算没有意义

可以对指针的算术运算

  1. 给指针加、减、一个整数(+、+=、-、-=)
  2. 递增递减(++/--)
  3. 两个指针相减
#include <stdio.h>

int main(void)
{
	char ac[] = {0,1,2,3,4,20,6,7,8,9,};
	char *p1 = &ac[5];
	char *p2 = &ac[4];

	printf("p1 - p2 = %d\n" , p1 - p2);
	printf("*p1 - *p2 = %d\n" , *p1 - *p2);
	// p1 - p2 = 1   也就是 5 -4
    // *p1 - *p2 = 16  也就是 20 - 4

    
	int ai[] = {0,1,2,3,4,5,6,7,8,18,};
	int *q1 = &ai[9];
	int *q2 = &ai[2];

	printf("q2 - q1 = %d\n", q2 - q1);
	printf("*q2 - *q1 = %d\n", *q2 - *q1);
	// q2 - q1 = -7      也就是 2 - 9
	// *q2 - *q1 = -16   也就是 2 - 18

	return 0;
}

*p++

  1. 取出p所指的那个数据来,完事之后顺便把p移动到下一个位置
  2. *的优先级虽然高,但没有++高
  3. 常用于数组类的连续空间操作
  4. 在某些cpu上,这可以直接被翻译成一条汇编指令
	char ac[] = {0,1,2,3,4,20,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; p++)
	// {
	// 	printf("%d\n", *p);
	// }

	// printf("---------------\n");
	
	// 使用*p++,快
	while(*p != -1){
		printf("%d\n", *p++);
	}

0地址

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

指针的类型

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

用指针来做什么

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

七、动态内存分配

输入数据

  1. 如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
  2. C99可以用变量做数组定义的大小,C99之前呢?
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int number;
	int *a;
	int i;
	printf("请输入数量:\n");
	scanf("%d" , &number);

	// 动态内存分配用malloc,此时a就相当于数组
	a = (int*)malloc(number*sizeof(int));

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

	for (i = number - 1; i >=0; i--)
	{
		printf("%d\n", a[i]);
	}

	// 最后释放空间
	free(a);

	return 0;
}

malloc函数

#include <stdlib.h>
void* malloc(size_t size);
  1. 向malloc申请的空间的大小是以字节为单位的,需要什么类型就在sizeof里面写什么类型
  2. 返回的结果是void* , 需要类型转换为自己需要的类型
  3. 最后释放空间
(int*)malloc(n*sizeof(int));

没空间了?

  1. 如果申请失败则返回0 , 或者叫做NULL
  2. 你的系统可以给你多大的空间?
#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);

	return 0;
}
// 分配了27200MB的空间

free()

  1. 把申请得来的空间还给系统
  2. 只能还申请的空间的首地址
  3. 一个malloc对应一个free
posted @ 2019-10-02 18:39  达达i  阅读(529)  评论(0编辑  收藏  举报