c语言指针的基础学习与了解

基础概念

在学习指针之前我们先了解一些基础概念以便我们更深入理解什么是指针

  1. 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。

  2. 内存:内部存贮器,暂存程序/数据——掉电丢失,常见的内存有: SRAM、DRAM、DDR、DDR2、DDR3。

  3. 外存:外部存储器,长时间保存程序/数据—掉电不丢,常见的外存有:ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。

其中,内存是沟通CPU与硬盘的桥梁:

  • 暂存放CPU中的运算数据
  • 暂存与硬盘等外部存储器交换的数据

有关内存的两个概念:物理存储器和存储地址空间。

  1. 物理存储器:实际存在的具体存储器芯片。
  • 显示卡上的显示RAM芯片

  • 各种适配卡上的RAM芯片和ROM芯片

  • 主板上装插的内存条

  1. 存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义。
  • 编码:对每个物理存储单元(一个字节)分配一个号码

  • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

  1. 内存地址
  • 将内存抽象成一个很大的一维字符数组。

  • 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。

  • 这个内存编号我们称之为内存地址。

内存中的每一个数据都会分配相应的地址:

  • char:占一个字节分配一个地址
  • int: 占四个字节分配四个地址
  • float、struct、函数、数组等

指针和指针变量

  • 内存区的每一个字节都有一个编号,这就是“地址”。
  • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
  • 指针的实质就是内存“地址”。指针就是地址,地址就是指针。
  • 指针是内存单元的编号,指针变量是存放地址的变量。

通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样

指针是一种数据类型,指针变量则是一种变量

指针变量指向谁,就把谁的地址赋值给指针变量

指针变量的定义及使用

指针变量的定义形式如:数据类型 *指针名;

例如:

//分别定义了 int、float、char 类型的指针变量
int *x;
float *f;
char *ch;

x、f、ch为指针变量名,而不是*x、*f、*ch

指针变量的使用:

  • 取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
  • 指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。该操作符操作的是指针变量指向的内存空间
#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里面,所以是没有地址的。

通过指针间接修改变量的值

	int a = 0;
	int b = 11;
	int *p = &a;

	*p = 100;
	printf("a = %d, *p = %d\n", a, *p);

	p = &b;
	*p = 22;
	printf("b = %d, *p = %d\n", b, *p);

指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8

  • sizeof()测的是指针变量指向存储地址的大小

  • 在32位平台,所有的指针(地址)都是32位(4字节)

  • 在64位平台,所有的指针(地址)都是64位(8字节)

野指针和空指针

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

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

指针变量的初始化

  • 指针变量与其它变量一样,在定义时可以赋值,即初始化。

  • 也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。

万能指针void *

void *指针可以指向任意变量的内存空间

	void *p = NULL;

	int a = 10;
	p = (void *)&a; //指向变量时,最好转换为void *

	//使用指针变量指向的内存时,转换为int *
	*( (int *)p ) = 11;
	printf("a = %d\n", a);

const修饰的指针变量

	int a = 100;
	int b = 200;

	//指向常量的指针
	//修饰*,指针指向内存区域不能修改,指针指向可以变
	const int * p1 = &a; //等价于int const *p1 = &a;
	//*p1 = 111; //err
	p1 = &b; //ok

	//指针常量
	//修饰p1,指针指向不能变,指针指向的内存可以修改
	int * const p2 = &a;
	//p2 = &b; //err
	*p2 = 222; //ok

在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用const修饰指针数据类型。(但用const定义常量往往是不安全的)

指针和数组

数组名字是数组的首元素地址,但它是一个常量:

	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);

	//a = 10; //err, 数组名只是常量,不能修改

指针操作数组元素

#include <stdio.h>
int  main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);
	for (i = 0; i < n; i++)
	{
		//printf("%d, ", a[i]);
		printf("%d, ", *(a+i));
	}
	printf("\n");

	int *p = a; //定义一个指针变量保存a的地址
	for (i = 0; i < n; i++)
	{
		p[i] = 2 * i;
	}

	for (i = 0; i < n; i++)
	{
		printf("%d, ", *(p + i));
	}
	printf("\n");
	return 0;
}

注:数组名不等价于指针变量,指针变量可以进行 p++和&操作,而这些操作对于数组名是非法的。数组名在编译时是确定的,在程序运行期间算一个常量。

指针运算

  1. 赋值运算

指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址

  1. 指针与整数的加减运算
  • 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
#include <stdio.h>

int main()
{
	int a;
	int *p = &a;
	printf("%d\n", p);
	p += 2;//移动了2个int
	printf("%d\n", p);

	char b = 0;
	char *p1 = &b;
	printf("%d\n", p1);
	p1 += 2;//移动了2个char
	printf("%d\n", p1);

	return 0;
}
  • 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
#include <stdio.h>

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int *p2 = &a[2]; //第2个元素地址
	int *p1 = &a[1]; //第1个元素地址
	printf("p1 = %p, p2 = %p\n", p1, p2);

	int n1 = p2 - p1; //n1 = 1
	int n2 = (int)p2 - (int)p1; //n2 = 4
	printf("n1 = %d, n2 = %d\n", n1, n2);
	
	return 0;
}
  1. 关系运算

假设有指针变量 px、py。

  • px > py 表示 px 指向的存储地址是否大于 py 指向的地址

  • px == py 表示 px 和 py 是否指向同一个存储单元

  • px == 0 和 px != 0 表示 px 是否为空指针

//定义一个数组,数组中相邻元素地址间隔一个单元
int num[2] = {1, 3};

//将数组中第一个元素地址和第二个元素的地址赋值给 px、py
int *px = &num[0], *py = &num[1];
int *pz = &num[0];
int *pn;

//则 py > px
if(py > px){
	printf("py 指向的存储地址大于 px 所指向的存储地址");
}

//pz 和 px 都指向 num[0]
if(pz == px){
	printf("px 和 pz 指向同一个地址");
}

//pn 没有初始化
if(pn == NULL || pn == 0){
	printf("pn 是一个空指针");
}

多级指针

  1. C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。

  2. 指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针,二级指针就是指向一个一级指针变量地址的指针。

  3. 三级指针基本用不着

	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

指针表达式

一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。int a,b;

int a,b;
int array[10];
int *pa;
pa=&a; //&a 是一个指针表达式。
Int **ptr=&pa; //&pa 也是一个指针表达式。
*ptr=&b; //*ptr 和&b 都是指针表达式。
pa=array;
pa++; //这也是指针表达式。

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

指针和函数

  1. 函数形参改变实参的值
#include <stdio.h>

void swap1(int x, int y)
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
	printf("x = %d, y = %d\n", x, y);
}

void swap2(int *x, int *y)
{
	int tmp;
	tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 3;
	int b = 5;
	swap1(a, b); //值传递
	printf("a = %d, b = %d\n", a, b);

	a = 3;
	b = 5;
	swap2(&a, &b); //地址传递
	printf("a2 = %d, b2 = %d\n", a, b);

	return 0;
}
  1. 数组名做函数参数
#include <stdio.h>

void printArrary(int *a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(a) / sizeof(a[0]);

	//数组名做函数参数
	printArrary(a, n); 
	return 0;
}
  1. 指针做为函数的返回值
#include <stdio.h>
int a = 10;
int *getA()
{
	return &a;
}
int main()
{
	*( getA() ) = 111;
	printf("a = %d\n", a);

	return 0;
}

指向函数的指针

C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。

数据类型 (*函数指针名)();

函数指针在进行“*”操作时,可以理解为执行该函数。函数指针不同与数据指针,不能进行+整数操作。

例如:

#include <string.h>
/**
*	定义一个方法,传入两个字符串和一个函数指针 p,用 p 对两个字符串进行操作
*/
void check(char *x, char *y, int (*p)());
void main(){
	//string.h 库中的函数,使用之前需要声明该函数。字符串比较函数
	int strcmp();
	char x[] = "Zack";
	char y[] = "Rudy";
	
	//定义一个函数指针
	int (*p)() = strcmp;

	check(x, y, p);
}
void check(char *x, char *y, int (*p)()){
	if(!(*p)(x, y)){
		printf("相等");
	}else{
		printf("不相等");
	}
}

利用函数指针调用方法具体操作如下:

(*p)(x, y);

参考资料

黑马程序员C语言基础教程[源码,笔记,软件,案例全,初学者值得收藏的教程]_哔哩哔哩_bilibili

让你不再害怕指针—C指针详解(经典,非常详细)_CSDN博客_指针

深入理解C语言指针 CSDN博客_c语言的指针怎么理解

posted @ 2022-10-21 20:13  书文阁下  阅读(132)  评论(0编辑  收藏  举报