课件

1、内存、地址与指针(第一节)

hello大家好,接下来就由我带领大家认识一下指针的世界。

1.1内存、地址

在介绍指针之前,先讲一讲计算机内存与地址。(换ppt)计算机内存可以看作是一辆火车,我们知道火车有很多节车厢,一节车厢就可以看作是一小块内存,坐过火车的人都知道,每一节车厢都有它自己的车厢编号,每一节车厢里呢,又有很多座位,每个座位也有属于他自己的独一无二的编号,通过座位号就可以唯一的确定一个座位,座位号就好像这一个内存块的偏移量,通过它可以唯一确定的数据存储的位置。

在这里注意一下,内存中的每个位置都有一个独一无二的地址标识,就好比火车上的座位都只有唯一的一个座位号与它对应一样,我们知道,火车票上都写着所对应的座位号,我们也应该知道,不可能一个座位对应着好几张火车票,也不可能一个火车票对应着很多座位。在计算机中也一样,内存中的每个位置都有一个独一无二的地址标识,内存中的每个位置都包含一个值。就像我们乘火车的时候,每个座位都有它自己唯一的座位号,这个座位号就是地址,每个座位号都对应一个座位,这个座位就是内存中的一个字节

好,简单了解了内存的定义以后,接下来我们讲一下什么是指针,什么是指针变量

-换ppt

1.2什么是指针,指针变量

上面我们已经简单了解了c语言中地址的含义了,我们要学的指针呢,其实就是地址的别称,也就是说,指针就是地址。

凡是出现“指针”的地方,都可以用“地址”代替,例如,变量的指针就是变量的地址。指针是地址的形象化,指针的意思是通

过它能找到以它为地址的内存单元。

我们来看这个例子,刚刚我说过,指针就是地址,凡是出现“指针”的地方,都可以用“地址”代替。在图中内存区域里,每个字节都对应着一个独一无二的地址标识,而这个地址,就是指针。我们再看下面这个例子,这里用了一个房子代表了一个内存块,房子的唯一的标识就是它的门牌号,右边这个小人儿想要去找住在某某路12号的小明该怎么走呢,很显然,知道了房子的地址,找到小明就只是时间的问题了。我们的指针就是扮演着指路的作用。在这里我需要强调一下,我们学习指针,必须要弄清楚储存单元的地址和储存单元的内容这两个概念的区别,就在这个例子中,房子的门牌号就相当于储存单元的地址,而房子里住的什么人,小明还是小红,就是储存单元的内容了。

我们知道,在c语言程序中,可以定义整型变量、浮点型变量、字符型变量等,同样的,我们也可以定义用来存放地址的变量,这就是指针变量。我们需要注意一下,有了基本数据类型的变量,就可以有指向这些类型变量的指针,就像这些例子,有了整形的变量,就可以有一个指向整形变量的指针,有了浮点型的变量,就可以有一个指向浮点型变量的指针。因此,指针变量是基本数据类型派生出来的类型,它不能离开基本类型而独立存在。大家看一下这个注意事项。

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;     /* 一个字符型的指针 */

1.3操作符‘*’和‘&’

然后我介绍一下涉及到指针的两个运算符,取地址运算符和指针运算符,取地址运算符,顾名思义,就是取一个变量的地址,也就是取一个变量的指针,地址就是指针嘛,通过取地址运算符,就可以得到变量的地址。指针运算符也叫间接访问运算符,也就是说,通过指针运算符,可以得到指针所指向的对象,例如指针运算符加p代表指针变量p指向的对象。下面这两个例子,第一个呢就是把指针变量p指向的对象赋值给了K,第二个是把1赋值给了指针变量p指向的对象,也就是变量A。

然后给大家介绍一下怎么定义指针变量?和怎么引用指针变量?

定义指针变量的一般形式为:
类型名 * 指针变量名;注意一下,这里的* 是和变量名一起的而不是和类型名一起的,举个例子,(打开vc)int * a, b;这里只有a是指针变量,而b是整形变量。
引用指针变量的基本方式有这三种,
1)给指针变量赋值
p=&a;这里把a的地址赋值给了p
2)引用指针变量指向的变量
printf("%d",*p),这里用指针运算符把p所指向的对象用%d的形式打印了出来
3)引用指针变量的值
printf("%o",p),这里我们直接打印了p这个指针变量的值
在这里插入图片描述

在c语言程序中,有两种访问变量的方式,分别是直接按变量名进行的访问的“直接访问”方式。
和将变量i的地址存放在另一变量中,然后通过该变量来找到变量i的地址,从而访问i变量的“间接访问”方式

然后我们看一下他们对应的存取方式,第一种直接存取,将数字三直接存在了地址为2000的变量i中

第二种间接存取,i——pointer是一个指针变量,他的值为2000,我们用指针运算符得到这个指针所指向的对象,然后将3存入到了地址为2000的变量中

1.4 空指针,野指针

ok,接下来我介绍几种特殊的的指针,首先是野指针

野指针是指向一个已删除的对象或未申请访问受限内存区域的指针。
任何指针变量在刚被创建时如果未被赋值,它将会乱指一气,他有可能指向一个已经删除了的对象,或者系统中某个不能访问的关键的位置。这时候如果对未初始化的指针进行调用可能会发生莫名奇妙的错误。
如果定义指针变量的时候,还没想好给他赋予什么值,这时候我们可以借助NULL来对它赋值。标准 C 专门定义了一个标准预处理宏 NULL,其值为“空指针常量”,通常是 0 或者“((void*)0)”。 NULL 是可以赋值给任何类型指针的值,它的类型为 void*,而不是整数 0。这样,我们在定义指针变量的时候,如果没有实现想好要赋予什么值,就可以用NULL在赋值了。

(运行代码删掉NULL演示一遍)

#include <stdio.h>
 
int main ()
{
   int  *ptr = NULL;
 
   printf("ptr 的地址是 %p\n", ptr  );
 
   return 0;
}

1.5 void指针

void 指针是一种特殊的指针,它表示为“无类型指针”。由于 void 指针没有特定的类型,因此它可以指向任何类型的数据。也就是说,任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换,如下面的示例代码所示:

    void *p1;
    int *p2;
    …
    p1 = p2;

虽然如此,但这并不意味着可以无需任何强制类型转换就将 void 指针直接赋给其他类型的指针,因为“空类型”可以包容“有类型”,而“有类型”则不能包容“空类型”。正如我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”一样。
我们看一下错误代码,这里面p1是void指针它不可以赋值给指向整型的

    void *p1;
    int *p2;
    …
    p2 = p1;

由此可见,要将 void 指针赋值给其他类型的指针,必须进行强制类型转换。如下面的示例代码所示:

    void *p1;
    int *p2;
    …
    p2 = (int*)p1;

注意:1、避免对void指针进行算术操作
进行算法操作的指针必须确定知道其指向数据类型大小,也就是说必须知道内存目的地址的确切值。如下面的示例代码所示:我来运行一下这段代码看一下会出现什么样的结果

    char a[20]="qwertyuiopasdfghjkl";
    int *p=(int *)a;
    p++;
    printf("%s", p);

大家可以看到我们输出的字符串是从t开始的,这是为什么呢?

在上面的示例代码中,指针变量 p 的类型是“int*”,指向的类型是 int,被初始化为指向整型变量 a。

在执行语句“p++”时,编译器是这样处理的:把指针 p 的值加上了“sizeof(int)”(由于在 32 位系统中,int 占 4 字节,所以这里是被加上了 4),即 p 所指向的地址由原来的变量 a 的地址向高地址方向增加了 4 字节。但又由于 char 类型的长度是一个字节,所以语句“printf("%s",p)”将输出“tyuiopasdfghjkl”。
而对于 void 指针,编译器并不知道所指对象的大小,所以对 void 指针进行算术操作都是不合法的,但在GCC编译器中,指定“void*”的算法操作与“char*”一致。在真实的设计环境中,尽可能符合 ANSI 标准,尽量避免对 void 指针进行算术操作。

2、如果函数的参数可以是任意类型指针,应该将其参数声明为 void*
前面提到,void 指针可以指向任意类型的数据,同时任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换。因此,在编程中,如果函数的参数可以是任意类型指针,那么应该使用 void 指针作为函数的形参,这样函数就可以接受任意数据类型的指针作为参数。

1.6 案例分析

定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值

#include <stdio.h>
 
int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("Address of var variable: %p\n", &var  );
 
   /* 在指针变量中存储的地址 */
   printf("Address stored in ip variable: %p\n", ip );
 
   /* 使用指针访问值 */
   printf("Value of *ip variable: %d\n", *ip );
 
   return 0;
}

2、指针进阶(第二节)

hello大家好,这节课我们讲一下

2.1指针的算数运算

C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、–、+、-。

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中的数组地址 */
   ptr = var;
   for ( i = 0; i < MAX; i++)
   {
 
      printf("存储地址:var[%d] = %x\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
 
      /* 移动到下一个位置 */
      ptr++;
      //ptr--;
   }
   return 0;
}

指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中第一个元素的地址 */
   ptr = var;
   i = 0;
   while ( ptr <= &var[MAX - 1] )
   {
 
      printf("Address of var[%d] = %p\n", i, ptr );
      printf("Value of var[%d] = %d\n", i, *ptr );
 
      /* 指向上一个位置 */
      ptr++;
      i++;
   }
   return 0;
}

2.2通过指针引用数组(多维数组)

数组元素的指针,一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用储存单元,它们都有相应的地址,指针变量既然可以指向变量,当然也可以指向数组元素。所谓数组元素的指针就是数组元素的地址,

int a[10]={1,3,5,7,9}
int * p=&a[0];
//int * p=a;

以上是使用指针变量p指向a数组的第0号元素
引用数组元素可以用下标法,也可以用指针法,即通过指向数组元素的指针找到需要的元素。使用指针法能使目标程序质量高。
注意:在c语言中,数组名代表数组首元素的地址。上述“int *p=a”的作用是把a数组的首元素的地址赋给指针变量p,而不是把数组a各元素的值赋给p。

int a[10]={1,3,5,7,9}
int * p=&a[0];
int * q=a;
printf("%d",*p);
printf("%d",*p[2]);
printf("%d",*q[2])

&通过指针引用多维数组
在这里插入图片描述

2.3通过指针引用字符串

在c程序中,字符串是存放在字符数组中的。想引用一个字符串,可以用两种方法。
(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明“%s”输出该字符串。
(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。

2.4案例分析

将字符串复制为字符然后输出
**解题思路:**定义两个字符数组a和b,用数组初始化将数组中的字符逐个复制到数组中可以用不同的方法引用输出数组元素今用地址法算出各元素的值。
编写程序:

# include <stdio.h>
int main()
{
	char a[] = "i am a student.", b[20];//定义字符数组
	int i;
	for (i = 0; *(a + i) != '\0'; i++)
		*(b + i) = *(a + i);//将a[i]的值赋给b[i]
	*(b + i) = '\0';//在b数组的有效字符之后加‘\0’
	printf("string a is:%s\n", a);//输出a数组中全部有效字符
	printf("string b is:");
	for (i = 0; b[i] != '\0'; i++)
		printf("%c", b[i]);//逐个输出b数组中全部有效字符
	printf("\n");
	return 0;

}

程序分析:程序中a和b都定义为字符数组,今通过地址访问其数组元素。在for语句中,先检查a[i]是否为‘\0’(a[i]是以*(a+i)形式表示的)如果不等于’\0’,表示字符串尚未处理完就将a[i]的值赋给b[i],即复制一个字符。在for循环中将a串中的有效字符全部复制给了b数组最后还应将‘\0’复制过去,作为字符串结束标志。
在第2个for循环中用下标法表示一个数组元素(即一个字符).可以用输出a数组的方法输出b数组。
printf(“string b is:%s\n”,b);
程序中用逐个字符输出的方法只是为了表示可以用不同的方法输出字符串。
也可以用另一种方法–指针变量访问字符。通过改变指针变量的值使它指向符串中的不同字符,
用指针变量来处理上述问题
解题思路:定义两个指针变量p1和p2分别指向字符数组a和b。改变指针变量p1和p2的值,使它们顺序指向数组中的各元素,进行对应元素的复制。

int main()
{
	char a[] = "i am a student.", b[20],* p1,*p2;
	p1 = a; p2 = b;
	for(; *p1 != '\0'; p1++, p2++)
		* p2 = * p1;
	*p2 = '\0';
	printf("string a is:%s\n", a);//输出a数组中全部有效字符
	printf("string b is:%s\n",b);//输出b数组中全部有效字符

	return 0;

}

在这里插入图片描述在这里插入图片描述

3、指针进阶二(第三节)

3.1指针数组

在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int i, *ptr[MAX];
 
   for ( i = 0; i < MAX; i++)
   {
      ptr[i] = &var[i]; /* 赋值为整数的地址 */
   }
   for ( i = 0; i < MAX; i++)
   {
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   return 0;
}

可以用一个指向字符的指针数组来存储一个字符串列表,如下:

#include <stdio.h>
 
const int MAX = 4;
 
int main ()
{
   const char *names[] = {
                   "Zara Ali",
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
   int i = 0;
 
   for ( i = 0; i < MAX; i++)
   {
      printf("Value of names[%d] = %s\n", i, names[i] );
   }
   return 0;
}

11

3.2多级指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
在这里插入图片描述一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

#include <stdio.h>
 
int main ()
{
   int  var;
   int  *ptr;
   int  **pptr;

   var = 3000;

   /* 获取 var 的地址 */
   ptr = &var;

   /* 使用运算符 & 获取 ptr 的地址 */
   pptr = &ptr;

   /* 使用 pptr 获取值 */
   printf("Value of var = %d\n", var );
   printf("Value available at *ptr = %d\n", *ptr );
   printf("Value available at **pptr = %d\n", **pptr);

   return 0;
}

&指针数组和数组指针的区别

指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组。

其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型。

在 32 位系统中,指针占四个字节。

数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针。

其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。

据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是种类型的变量。

3.3动态内存分配

什么是内存的动态分配
全局变量是分配在内存中的静态存储区的非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是个称为栈(stack)的区域除此以外C语言还允许建立内存动态分配区域以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放而是需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆(heap)区.可以根据需要,向系统申请所需大小的空间由于未在声明部分定它们为变量或数组因此不能通过变量名或数组名去引用这些数据只能通过指针来引用.
malloc函数
在这里插入图片描述

free函数
在这里插入图片描述

3.4总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值