12-指针02

指针

回顾

1. 指针的定义

  • 指针也是一种数据类型,指针变量也是一种变量
  • 指针变量指向谁,就把谁的地址赋值给指针变量
  • “*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>

int main()
{
	int a = 0;
	char b = 100;
	printf("%p, %p\n", &a, &b); //打印a, b的地址

	//int *代表是一种数据类型,int*指针类型,p才是变量名
	//定义了一个指针类型的变量,可以指向一个int类型变量的地址
	int *p;
	p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
	printf("%d\n", *p);//p指向了a的地址,*p就是a的值

	char *p1 = &b;
	printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值

	return 0;
}

注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
2.指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8
  • sizeof()测的是指针变量指向存储地址的大小
  • 在32位平台,所有的指针(地址)都是32位(4字节)
  • 在64位平台,所有的指针(地址)都是64位(8字节)
        int *p1;
	int **p2;
	char *p3;
	char **p4;
	printf("sizeof(p1) = %d\n", sizeof(p1));
	printf("sizeof(p2) = %d\n", sizeof(p2));
	printf("sizeof(p3) = %d\n", sizeof(p3));
	printf("sizeof(p4) = %d\n", sizeof(p4));
	printf("sizeof(double *) = %d\n", sizeof(double *));

1.野指针

野指针的指向的地址是随机的,不能操作野指针
指针指向的地址是系统分配的,定义变量的时候向系统申请,系统进行分配
指针初始化的地址不能由自己给出

#include <stdio.h>
#include <stdLib.h>
#include <string.h>

int main()
{
	
	// 野指针是指没有初始化(赋值)的指针
	
	int *p; // 定义一个指针变量p   
	// *p 指向的是指针变量存储的地址对应的空间
	// 指针变量p没有存储地址,所以p中存储的地址是随机的(指针的指向是随机的)
	// 将这样没有初始化的指针称之为野指针
	*p = 200;
	
	system("pause");
	return 0;
}

2.空指针

  • int *p = NULL -- 表明定义了一个指针,并将其初始化为NULL,就是指向0所在的地址0x0000 0000;
  • 零指针初始化所指向的位置不用于存储其他的地址,只是表明该指针进行了初始化,可以进行使用的标志
  • 习惯:将指针使用完之后,置为NULL,这样在使用指针的时候,判断指针指向为NULL,那么该指针可以进行使用,如果该指针不为NULL,表明该指针不能使用(该指针未初始化或者正在使用)

#include <stdio.h>
#include <stdLib.h>
#include <string.h>

int main()
{	
	// 定义一个整型变量,如果不知道存储什么值,可以将a初始化为0
	int a = 0;
	// 定义一个指针变量p,如果不知道指针存储谁的地址可以将其
	// 初始化为0指针,将指针指向0地址,0x00000000 -- NULL
	int *p = NULL;
	
	// *p = 200; // 也不对,p保存了0x000000000,0x00000000是进行初始化的标记,该地址指向的空间不存储值
	
	// NULL是指针初始化的标记,指针p指向NULL,表示该指针初始化了,该指针可以使用,
	// 如果指针 p !== NULL 表明该指针没有初始化,不能使用(或者该指针指向其他地址空间)
	// 好习惯:将指针用完之后将指针变量指向NULL
	if(p == NULL)  
	{
		p = &a;
	}
	system("pause");
	return 0;
}

野指针和空指针总结

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。

	int a = 100;
	int *p;
        // p中保存的地址是a的值
	p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
        
        // p中保存的地址是任意给的
	p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义

	*p = 1000;  //操作野指针指向未知区域,内存出问题,err

但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

int *p = NULL;

NULL是一个值为0的宏常量:

#define NULL    ((void *)0)

3. 万能指针

  • 可以保存任何地址的指针
  • void *p

#include <stdio.h>
#include <stdLib.h>
#include <string.h>

int main()
{	
	
	// 万能指针,可以保存任意的地址
	int a = 10;
        void *p;
        p = (void *)&a;
        // printf("%d\n",*p); // 编译出错,指针的类型是void *,取出的长度为sizeof(void)
        // sizeof(void) -- 编译器不知道多大 -- 编译出错
        
        // p指针变量保存的是a的地址,p的类型是void *类型
        // 想要取出p中的变量 -- 需要将p指针类型从void * 转为int *
        printf("%d\n",*(int *)p); // *(地址) -- 将地址进行强转 *((int *)地址)
        

        // 不可以定义void类型的变量
        // 但是可以定义void *类型,因为指针都是4个字节
        // void b; 出错
        void *b;
	
	system("pause");
	return 0;
}

  • 通过万能指针取内容的时候,需要将万能指针类型转变为取值的指针类型

4.const修饰的指针变量

  • const -- 修饰变量a之后,不能通过a变量名修改a在内存中的值
  • 但是可以通过地址进行修改a的内容
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{	
	// const修饰变量a,不能再通过a修改a内存里面的内容
	// 但是可以同过地址进行修改a的值
	const int a = 10;
	
	int *p = &a;
	*p = 100;
	
	printf("%d\n",a); // 100
	system("pause");
	return 0;
}
  • const int p = &a -- const修饰,不能通过*p修改指向空间的内容
  • int * const p = &a -- const修饰指针变量p,p中所保存的地址不可被修改

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

int main()
{
	int a = 10;
	
	int b = 20;
	// const修饰的是*还是变量p?
	// 这里修饰的是*
	const int *p = &a;  // 不能通过*p修改p所指向的空间的内容
	
	// *p = 100;  const修饰*之后,不能通过p修改p指向的空间的内容
	
	// const 修饰指针变量p
	// p保存的地址不可以被修改
	int * const p = &a;   
	p = &b;  // p保存的地址不可以被修改 
	
        // p本身的指向不能改变,不同通过*p修改p所指向的空间的内容
	const int *const p = &a;        

	system("pause");
	return 0;
}

5.多级指针


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

int main()
{
	int a = 10;
	// 定义指针变量p保存a的地址
	// *p  int a  int *p
	int *p = &a;
	
	// 想要定义一个变量保存p的地址
	// int *q   int *p    int *(*q)
	int **q = &p;
	
	// 通过q得到a的值
	// 通过q得到a的地址 -- *q 表示p的地址的内容就是a的地址
	// **q -- 取a地址中的内容
	// **q = *(*q) = *(p) = a
	// **q = *(*q) = *(&a) = a  
	// *(地址) 表示取地址中的内容
	// *与&相遇 -- 相互抵消
	
	
	// 定义变量保存q的地址
	// *k int **q  int ***key
	int ***k = &q;
	
	// ***k = *(**K) = *(*(*k)) = *(*(&p)) = *p = *(&a) = a
	
	int **********g;
	int ***********h = &g;  // 保存指针类型变量地址,多一个*
	
	// ********** *h = ********** *(&g)  = *********g
	
	//分析
	// *与符号结合,表示变量k是一个指针变量
	// k是一个变量
	// k的类型
	// k用来保存地址的类型,将k与最近的*进行拖黑,剩下的就是保存地址的类型	
	system("pause");
	return 0;
}
	int a = 10;
	int *p = &a; //一级指针
	*p = 100; //*p就是a

	int **q = &p;
	//*q就是p
	//**q就是a

	int ***t = &q;
	//*t就是q
	//**t就是p
	//***t就是a

6. 指针与数组

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

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	// a 数组名 首元素的地址 - a = &a[0]
	// 知道首元素地址 --> a+1 = &a[1]
	// 定义指针保存首元素地址 -- 首元素地址类型是int
	// a 数组名只能指向首元素地址,通过指针可以指向数组中其他元素地址
	int *p = a; // &a[0]
	// *p -- 可以取a[0]的值
	
	int *q = &a[5]; 
	// *q 可以取a[5]的值	
	
	system("pause");
	return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	// a 数组名 首元素的地址 - a = &a[0]
	// 知道首元素地址 --> a+1 = &a[1]
	// 定义指针保存首元素地址 -- 首元素地址类型是int
	// a 数组名只能指向首元素地址,通过指针可以指向数组中其他元素地址
	
	// 操作数组元素
	
	int *p = a;//指针p保存的是首元素地址&a[0]
	for(int i = 0;i<sizeof(a)/sizeof(a[0];i++)
	{	
		// 可以遍历数组的元素
		// printf("%d\n",a[i])
		printf("%d\n",*(p+i)); // p变量保存的是首地址,地址+1跳过一个元素,4个字节
		*(p+i) = i; //数组元素赋值
		
	}
	
	system("pause");
	return 0;
}

例题

int main()
{
  int a[5] = {1,2,3,4,5};
  int *ptr = (int *)(&a+1);
  printf("%d %d\n",*(a+1),*(ptr-1)); // 2 5
}
  • 数组地址加一,跨一个数组
  • 元素地址加一跨一个元素
  • 指针加一跨元素类型步长
  • int *p -- 步长就是sizeof(int)\
  • 要得到内存的数据,就要先得到数据的地址 -- *(地址)得到的是地址中的内容
  • *(指针变量) -- 指针变量存储的是地址 -- *(地址)-- 得到指针变量存储的地址的内容

7.指针的运算

  • 指针的加减运算与步长有关
  • 指针变量相加没有意义
  • 指针变量相减可以得到之间的数组元素
  • 两个指针的变量相减,需要保证指针的类型一致
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int *p = a;
	// &a+1跨整个数组
	// &a+1的步长是整个数组,想要前一个元素的地址,在减去一个int类型的变量长度
	// 将&a+1地址转变为int * -- 表示其步长为sizeof(int)
	// (int *)(&a+1) - 1 得到数组最后一个元素地址
	// 得到数组的最后一个元素,(int *)(&a+1) 
	int *q = (int *)(&a+1)-1;
	
	// 指针相减 -- 得到之间元素的个数
	printf("%d\n",q-p); // 9
	printf("%d\n",*(p+3)); // 取第四个元素的值,4
	
	// c语言中,两个指针变量相加是没有意义的
	printf("%d\n",p+q); // 报错
	system("pause");
	return 0;
}

8.[]使用

  • []不一定只给数组使用
  • [] == *()
  • p[0] == *(p+0);
#include <stdio.h>

int mian()
{
	// [] 不是数组的专属
	// int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int a = 10;
	int *p = &a;
	
	// [] == *() 
	// p[0] == *(p+0);
	p[0] = 100;
	
	// 这里a = 100,表示p[0] = 100,对其内容进行操作
	printf("a = %d\n",a); // a = 100
	
	system("pause");
	return 0;
}

数组中的使用

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

int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int *p = a;//指针p保存的是首元素地址&a[0]
	for(int i = 0;i<sizeof(a)/sizeof(a[0];i++)
	{	
		// 可以遍历数组的元素
		// printf("%d\n",a[i])
		// printf("%d\n",*(p+i)); // p变量保存的是首地址,地址+1跳过一个元素,4个字节
		// printf("%d\n",p[i]); // p[i] == *(p+i);
		printf("%d\n",*(a+i)); //  a[i] == *(a+i); a是首元素地址,加几跨过几个元素
		
	}
	
	system("pause");
	return 0;
}

9.指针数组

指针数组,它是数组,数组的每个元素都是指针类型。

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

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	
	// 保存三个int类型数据的指针需要定义三个指针
	// 如果数据多了之后就比较麻烦
	// int *p = &a int *q = &b  int *k = &c
	// 需求:数组中的每一个元素都是指针(地址)
	// 指针变量的类型 数组名[长度] = {初始化值}; 初始化的是地址
	int *num[3] = {&a,&b,&c};
	
	// int *占4个字节
	printf("%d\n",sizeof(num)); // 12
	
	// 用指针数组或区域元素
	// 打印a的地址
	printf("a:\n",*num[0]);
	printf("b:\n",*num[1]);
	printf("c:\n",*num[2]);
	
	
	for(int i = 0;i<sizeof(num)/sizeof(num[0]);i++)
	{
		printf("a:\n",*num[i]);
	}
	
	// 定义指针保存num首元素的地址
	// num[0] -- 是int *类型
	// 保存首元素地址 -- 保存int * 类型的元素 -- 多一级* -- 二级指针
	// int **
	int **k = num;
	
	// 通过k将a的值取出
	// **k
	
	// 得到b的值
	// k+1得到的是&num[1]
	// *[k+1] -- 得到b的地址
	// **[k+1] -- b元素
	
	
	system("pause");
	return 0;
}
posted @ 2023-03-24 23:45  Icer_Newer  阅读(16)  评论(0编辑  收藏  举报