指针(C语言笔记)

一、引子与预备知识

在程序设计过程中, 无论是存入数据还是取出数据都需要与内存单元打交道, 计算机通过地址编码来表示内存单元。

指针类型就是为了处理计算机的地址数据的。

指针除了能够提高程序的效率, 更重要的作用是能使一个函数访问另一个函数的局部变量,因此指针是两个函数进行数据交换必不可少的工具
Alt

程序编译后,未执行程序前,分为两个区域
1、代码区:
存放CPU执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,防止程序意外修改它的指令
2、全局区:
全局变量和静态变量存放于此
全局区还包含了常量区,字符串常量和其他常量也存放于此
这个区域的数据在程序结束后由操作系统释放
程序执行后,分为两个区域
3、栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
4、堆区:
由编程者分配释放,如果编程者不释放,程序结束时由系统回收
比如:int p=(int )malloc(sizeof(int)10)表示申请了一块40个字节的堆区空间,然后申请完记得用free释放。

例如:

#include<stdio.h>
#include<string.h>
int a = 10;
static int b = 20;
void fun(int x)
{
	char *p = "Hello";//当你想要获取变量的内存地址时,使用 & 运算符;当你想要将字符串常量赋给指针变量时,直接将字符串常量赋值给指针变量即可,无需使用 & 运算符。之后会解释。
	printf("形参x的地址=%d\n\n", &x);
	printf("Hello的地址=%d\n\n", "Hello");
	printf("指针变量p的地址=%d\n\n", &p);
}

int main(int argc,const char *argv[])
{
	int c = 10;
	int d = 20;
	static int e = 30;
	char *p = "Hello";
	printf("\n全局变量a的地址=%d\n\n", &a);
	printf("静态全局变量b的地址=%d\n\n", &b);
	printf("静态局部变量e的地址=%d\n\n", &e);
	printf("字符串\"Hello\"的地址=%d\n\n", "Hello");
	printf("局部变量c的地址=%d\n\n", &c);
	printf("局部变量d的地址=%d\n\n", &d);
	printf("指针变量p的地址=%d\n\n", &p);
	fun(5);
	return 0;
}

运行结果:

全局变量a的地址=12939312//全局区

静态全局变量b的地址=12939316//全局区

静态局部变量e的地址=12939320//全局区

字符串"Hello"的地址=12933028//全局区

局部变量c的地址=5240936//栈区

局部变量d的地址=5240924//栈区

指针变量p的地址=5240912//栈区

形参x的地址=5240700//栈区

Hello的地址=12933028//全局区

指针变量p的地址=5240684//栈区

在这里插入图片描述
总结:
全局区存放的是全局变量,静态变量,字符常量,const 修饰的全局变量。
栈区存放的是局部变量和函数的形参,以及一些代码的地址,栈区的内容是可以修改的。
堆区是由程序员手动申请和释放,用malloc函数申请,用free函数释放。

二、什么是指针

1.定义指针

~内存中的一个字节为一个存储单元( Byte)。
~存储单元的编号称为地址。
~变量的地址是指该变量所在存储区域的第一个字节(单元)的地址。

指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *p;

left

在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,p 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

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

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

2.& —— 取地址运算

功能:取得变量的内存地址

int *p, m ; 
    /* 定义 p 为指向 int 类型变量的指针,同时定义变量m */ 
   m=200 ; /* 将数值200赋给变量m */ 
   p=&m ; /* 将变量 m 的地址值赋给指针变量 p,此时称指针变量p指向变量m */ 

假定变量m 的地址是1040
在这里插入图片描述

3.*——间接存取运算

功能:访问指针所指向的变量

int *p , m=200 , n ;
         p=&m ;  /* p 指向整型变量 m */
         n=*p ;    /* 对p指针的间接访问运算*p就是p所指向的变量m,这里是m赋给 n */
        *p=100 ; /*将 100 赋给指针变量 p 所指向的变量 m */

三、使用指针

1.指针变量的初始化
初始化格式:类型说明符  *指针变量名=初始地址值 ;

例:

int a, b ;

int *p1=&a, *p2 ; 
               /* 在定义p1的同时给它赋值,令其指向a */

p2 = &b ;   
    /* 在p2定义完成后,用赋值语句给p2赋值,令其指向b */ 

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:

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

当上面的代码被编译和执行时,它会产生下列结果:

var 变量的地址: 0x7ffeeef168d8
ip 变量存储的地址: 0x7ffeeef168d8
*ip 变量的值: 20

说明:

  • 一个指针变量只能指向同一数据类型的变量,该数据类型是在定义指针变量时明确给定的。
    在这里插入图片描述

——也可理解为指针类型变量

#include <stdio.h>

int main( )
{   
printf("%d\n",sizeof(int *));
printf("%d\n",sizeof(float *));
printf("%d\n",sizeof(double *));
printf("%d\n",sizeof(char *));

return 0;
}

运行结果:

4
4
4
4
请按任意键继续. . .
  • 不要把地址值与整数类型值相混淆。
    例如:地址2000与整型量2000是两个不同的概念。
    地址是由系统分配的,不能确定你指定的地址一定存在或者空闲。

注意:
由于指针数据的特殊性, 其初始化和赋值运算是有约束条件的, 只能使用以下四种值:
(1) 0值常量表达式
(2) 相同指向类型的对象的地址。
(3) 相同指向类型的另一个有效指针
(4) 对象存储空间后面下一个有效地址, 如数组下一个元素的地址。

eg:

int a, z=0;
int *p1=a; //错误, 地址初值不能是变量
p1=z;//错误, 整型变量不能作为指针, 即使值为0
p1=4000; //错误, 整型数据不能作为指针
p1=NULL; //正确, 指针允许0值常量表达式
p1=0; //正确, 指针允许0值常量表达式 

int a, *p1;
double f, *p3;
p1=&a; //正确
p3=&f; //正确
p1=&f; //错误, p1和&f指向类型不相同 

int x, *px=&x; //正确
int *py=px; //正确, 相同指向类型的另一个指针 

int a[10], *px=&a[2]; //正确
int *py=&a[++i]; //正确, 相同指向类型的另一个指针 

2.直接访问与间接访问

(1) 直接访问

直接使用变量名来存取变量值称为直接访问。
C语言内部处理成,从变量属性表中取得变量m的地址1040,
通过该地址直接存取该内存空间中的值。

(2) 间接访问

通过变量的指针来存取它所指向的变量的值称为间接访问。
如果变量 p 存放着变量 m 的地址,C语言内部处理成,先从变量属性表中取得变量p的地址2000,从地址2000中的取得1040,它是变量m的地址,再访问地址1040中的内容,即变量m。

例:
m=5,n=m 直接存取 m
*p=5,n=*p 间接存取 m

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

四、C中的空指针、野指针

1.null指针

++++++++++++C 语言规定有效数据的指针不指向0单元。如果指针变量值为0,即NULL(在stdio.h中已定义),表示空指针,即不指向任何变量。

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针

请看下面的程序:

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

当上面的代码被编译和执行时,它会产生下列结果:

ptr 的地址是 00000000

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

如需检查一个空指针,您可以使用 if 语句,如下所示:

if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr)    /* 如果 p 为空,则完成 */

注意:

int *p=0;
*p=20; //空指针间接引用将导致程序产生严重的异常错误 

2.野指针

如果指针未经初始化, 或者没有赋值, 或者指针运算后指向未知对象, 直接访问内存地址,那么该指针是无效的。该类型指针被称为野指针。

int *p;
*p=100; //错误, p为无效指针, 不能间接引用 

一个指针曾经指向一个已知对象, 在对象的内存空间释放后,虽然该指针仍是原来的内存地址, 但指针所指已是未知对象, 称为“迷途指针” (dangling pointer) 。

五、C指针的算术运算

1.基本运算

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

假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:

ptr++

在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。

我们概括一下:
指针的每一次递增,它其实会指向下一个元素的存储单元。
指针的每一次递减,它都会指向前一个元素的存储单元。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。

2.递增一个指针

我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:

#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] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
 
      /* 指向下一个位置 */
      ptr++;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200

3.递减一个指针:

同样地,对指针进行递减运算,即把值减去其数据类型的字节数,如下所示:

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中最后一个元素的地址 */
   ptr = &var[MAX-1];
   for ( i = MAX; i > 0; i--)
   {
 
      printf("存储地址:var[%d] = %p\n", i-1, ptr );
      printf("存储值:var[%d] = %d\n", i-1, *ptr );
 
      /* 指向下一个位置 */
      ptr--;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[2] = 518a0ae4
存储值:var[2] = 200
存储地址:var[1] = 518a0ae0
存储值:var[1] = 100
存储地址:var[0] = 518a0adc
存储值:var[0] = 10

4.指针的比较

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

下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:

#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("存储地址:var[%d] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
 
      /* 指向上一个位置 */
      ptr++;
      i++;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[0] = 0x7ffeee2368cc
存储值:var[0] = 10
存储地址:var[1] = 0x7ffeee2368d0
存储值:var[1] = 100
存储地址:var[2] = 0x7ffeee2368d4
存储值:var[2] = 200

六、指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

#include <stdio.h>
 
int main ()
{
   int  V;
   int  *Pt1;
   int  **Pt2;
 
   V = 100;
 
   /* 获取 V 的地址 */
   Pt1 = &V;
 
   /* 使用运算符 & 获取 Pt1 的地址 */
   Pt2 = &Pt1;
 
   /* 使用 pptr 获取值 */
   printf("var = %d\n", V );
   printf("Pt1 = %p\n", Pt1 );
   printf("*Pt1 = %d\n", *Pt1 );
    printf("Pt2 = %p\n", Pt2 );
   printf("**Pt2 = %d\n", **Pt2);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var = 100
Pt1 = 0115FDF0
*Pt1 = 100
Pt2 = 0115FDE4
**Pt2 = 100

其中Pt1与*Pt2均指V的地址

七、指针做函数参数

C 语言允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。

#include<stdio.h>
void fun(int *a)
{
	int b=2;
	a=&b;//形参a的指向改变了!!!!!!
	*a=*a*2;
	printf("%d,",*a);
}
int main()
{
	int k=3,*p=&k;
	fun(p);
	printf("%d,%d\n",k,*p);
	return 0;
}

程序的运行结果:

4,3,3

下面的实例中,我们传递一个无符号的 long 型指针给函数,并在函数内改变这个值:

#include <stdio.h>
#include <time.h>
 
void getSeconds(unsigned long *par);

int main ()
{
   unsigned long sec;


   getSeconds( &sec );

   /* 输出实际值 */
   printf("Number of seconds: %ld\n", sec );

   return 0;
}

void getSeconds(unsigned long *par)
{
   /* 获取当前的秒数 */
   *par = time( NULL );
   return;
}

当上面的代码被编译和执行时,它会产生下列结果:

Number of seconds: 1683035102

能接受指针作为参数的函数,也能接受数组作为参数,如下所示:

#include <stdio.h>
 
/* 函数声明 */
double getAverage(int *arr, int size);
 
int main ()
{
   /* 带有 5 个元素的整型数组  */
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值  */
   printf("Average value is: %f\n", avg );
   
   return 0;
}

double getAverage(int *arr, int size)
{
  int    i, sum = 0;      
  double avg;          
 
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
 
  avg = (double)sum / size;
 
  return avg;
}

当上面的代码被编译和执行时,它会产生下列结果:

Average value is: 214.40000

八、指针和指向数组(元素)的指针

1.指针和一维数组

数组元素:类型相同且在内存中连续存放,
数组名是该存储区的起始地址,是地址常量。
例:

`int a[10];` 
//a 是数组的起始地址
//a 是 a[0] 元素的地址
(1)数组名

C语言规定, 数组名既代表数组本身, 又代表整个数组的地址, 还是
是数组首元素的地址值, 即a与第0个元素的地址&a[0]相同。
例如下面两个语句是是等价的。

p=a;
p=&a[0];

数组名是一个指针常量, 因而它不能出现在左值和某些算术运算中,
例如:

int a[10], b[10], c[10];
a=b; //错误, a是常量不能出现在左值的位置
c=a+b; //错误, a、 b是地址值, 不允许加法运算
a++; //错误, a是常量不能使用++运算 
(2)指向一位数组元素的指针变量
int a[10]/* a数组的元素为整型变量, */ 
 int *p;         /*那么 p 可以指向 a 数组的任意元素。 */    
p=a;
p++;
p++;
p--;
//若p=&a[6];则*p访问a[6]
//若p=&a[0];则*p访问a[0]
(3) 指针可进行的运算(加、减、比较运算)

(1)指针+数值 ( 运算的意义)
如果:p 是指针,n 是数值
则:p±n 表示 p 向后/向前移动 n 个元素位置。
即:结果 p 的值±n*size
size是p 的基类型量占用的存储字节数。
若 p 是 int 型指针,则 size 等于 4
若 p 是 char 型指针,则 size 等于 1
若 p 是 double 型指针,则 size 等于 8 …

(2)指针 - 指针 运算的意义
指向相同数据类型的指针变量可以相减,其结果
为两指针所指向地址之间数据的个数。
例:int *px ,*py , n , a[5] ;
px=&a[1]; py=&a[4];
n = py - px ; 结果:n 值为3
n = px - py ; 结果:n 值为 - 3

根据 C 语言的规范,指针相减的结果是以数组元素的个数为单位的,而不是以字节为单位。
因此,当计算 n = px - py; 时,得到的结果是以 int 类型为单位的差值,而不是以字节为单位。这解释了为什么结果是 -3 而不是 -12。
需要注意的是,指针相减的结果取决于指针类型的大小,并且只有在指针指向同一数组(或同一内存块)时才有意义。在其他情况下,指针相减的结果是未定义的。

(3) 指针的比较运算 (指针 比较 指针)
六种运算:
== != < <= > >=
指针的比较运算是两个指针所指向的地址之间的
比较运算,产生的结果为 0(假)和 1(真)。

2.指针和字符串

(1) 字符数组和字符指针

C 语言中可以用字符数组存放字符串,也可以定义一个指针指向一个字符串常数。

  • 定义一个字符数组存放字符串
  char str[10]="Hello!";

str是一维字符数组名,即起始地址。
字符数组输入输出:

scanf(“%s”, str); /不可输入带空格字符串/
gets(str); /可输入带空格字符串/
printf(“%s”, str); puts(str); /* 输出字符串 */

字符数组赋值:strcpy(str, "ABCD");
访问第i个字符 str[i], 或 *(str+i)
第i个字符的地址 &str[i], 或 str+i

(2)定义一个字符指针并令其指向一个字符串
char *strp="Hello!" ;   

等价于:

char *strp ;     
strp="Hello!"; 

“Hello!” 的值是该字符串的首地址。
输出字符串: printf(“%s”, strp);
注意 字符数组名(str)与字符指针(strp)的区别:

  char str[10], *strp;    

① str — 指针常量,不论是否对字符数组赋值,
数组空间已分配,str的指向明确。
strp — 指针变量,若不赋初值,则其指向不确定。
② 赋值: char str[10], *strp;

 strcpy(str, "ABC");      

str的指向不变,改变的是存储单元的内容。

strp="Hello!";      

strp 指向字符串常量“Hello!”的第一个字符 。

  strp=str;

strp 指向字符串数组str 的第一个字节 。

注意:
使用字符串指针易犯的错误:

(1) char str[10];  str="Hello!"; ×   

str是指针常量,不能给常量赋值。

(2) char str[10];  str[ ]="Hello!"; ×    

不能用赋值语句给数组整体赋值。
正确的:

#include <string.h>

int main() {
    char str[10];
    strcpy(str, "Hello!");

    printf("%s\n", str);  // 输出: Hello!

    return 0;
}

3.字符串指针作函数参数

请阅读并理解下述若干函数,它们的功能都是实现字符串复制。

void copy_string(char to[ ], char from[ ])
     {	
	int i=0 ;
	while(from[i]!='\0') 
	{  to[i]=from[i];  i++ ; }
	to[i]='\0' ;  
     }
     
 void copy_string(char to[ ], char from[ ])
     {	
	int i=0 ;
	while (  ( to[i]=from[i]) != '\0'   )  i++ ;  
     }
     
void copy_string(char *to, char *from)
{	
	while(  (*to=*from)!='\0'  ) 
	{  to++ ; from++ ; }  
}

 void copy_string(char *to, char *from)
{	
	while((*to++ = *from++)!='\0')   ;  
}

 void copy_string(char *to, char *from)
{	
	while(*to++ = *from++)   ;  
}

以上五个函数的功能是一样的,可以用下述主函数调用。

int main( )
{	
	char s1[20], s2[20];
	gets(s1);
	copy_string(s1, s2);
	puts(s2);
	return 0;
}

4.多维数组的指针

(1)二维数组的地址
指针和元素指针

例:有定义:int a[3][4];
在这里插入图片描述

将每行看成一个“大元素(即一维数组)”,
二维数组是这些“大元素”的数组,
即二维数组是数组的数组。

二维数组名 a 指向 a[0],
a 是行指针
a+1指向下一行, 即指向a[1]。

一维数组名a[0]指向a[0][0],
a[0]是元素指针
a[0]+1指向下一元素, 即指向a[0][1]。

行指针的角度来看:
*(a+i) <==> a[i]

元素指针的角度来看:
若欲访问a[i][j], 可以写成 *( a[i]+j )

所以访问元素a[i][j],也可写成 *(*(a+i)+j )

(2)二维数组中元素a[i][j]地址的表示:
  &a[i][j]    
   a[i]+j
   *(a+i)+j

在这里插入图片描述

(3)二维数组中元素值的表示方法:
   a[i][j]   
    *(a[i]+j)   
    *(*(a+i)+j)  
    (*(a+i))[j]
(4)行指针变量的类型
int (*p)[4]; int a[3][4]

定义一个行指针p,指向一维数组,该一维数组包含4个整型数值(相当于二维数组的一行元素)。

p=a ;     /* p和a的类型一样,类型为 int(*)[4]    */

==注意:==数组名 a 和 p 同为行指针,但 a 是指针常量,而 p 是指针变量。

(5)二维数组名作函数参数
int total(int(*p)[4], int n)   /* n 行数 *///参数传递时有int (*p)[4]=a 

{     int  i, j, sum=0;
      for( i=0; i<n; i++)
           for( j=0; j<4; j++)
               sum += *(*(p+i)+j) ;
      return sum;
}
int main( )
{    int a[3][4], sum, i, j ;
     for(i=0; i<3; i++)
          for(j=0; j<4; j++)  scanf("%d", a[i]+j); 
     sum = total( a, 3 );
     printf("sum=%d\n", sum); 
     return 0;         }
(6)将二维数组看成一维数组访问

eg:输出二维数组全体元素的值(通用):

void print(int *p, int row, int col)  /* row是行数, col是列数 */
{
	int i;
	for(i=0; i<row*col; i++, p++)
	{
		if(i%col==0)	  printf("\n"); 
		printf("%4d", *p);
	}
	printf("\n");
}
   

在这里插入图片描述

注意:
a和a[i]是两种类型不同的指针常量。
a[i]的类型是 int *
a的类型是 int (*)[4]

5.指针数组

在我们讲解指针数组的概念之前,先让我们来看一个实例,它用到了一个由 3 个整数组成的数组:

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int i;
 
   for (i = 0; i < MAX; i++)
   {
      printf("Value of var[%d] = %d\n", i, var[i] );
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:

int *ptr[MAX];

在这里,把 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;
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

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

#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;
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
(1)基本概念

在这里插入图片描述

(2)定义

定义格式:类型说明符 *指针数组名[元素个数]
例:int *p[10] ; 数组p 的10个元素 p[0], p[1] ……p[9] 都是指向 int 型数据的指针。
数组的10个元素都是 int * 类型的。

int  a=6, b=8, c=2;
        int  *p[10];      
        p[0] = &a;        p[1] = &b;      p[2] = &c; 
        printf("%d\t%d\t%d\n", *p[0], *p[1], *p[2]); 
(3)main 函数的参数(了解)
 //main( )函数的参数格式:
  int main( int argc, char *argv[ ] )
  {.   } 

形参argc:命令行中参数的个数
形参argv:指针数组的各元素分别指向命令行中
各字符串的首地址。

int main(int argc, char *argv[ ]) 
{ 
	int i; 
	printf("argc=%d\n", argc); 
	printf("Command name=%s\n", argv[0]); 
	for(i=1; i<argc; i++) 
		printf("%s\n", argv[i]); 
	return 0;
}

九、从函数返回指针

在上一章中,我们已经了解了 C 语言中如何从函数返回数组,类似地,C 允许您从函数返回指针。为了做到这点,您必须声明一个返回指针的函数,如下所示:

int * F1()//函数返回int型指针
{
.
.
.
}

另外,C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。

现在,让我们来看下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:

#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
 
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
   static int  r[10];
   int i;
 
   /* 设置种子 */
   srand( (unsigned)time( NULL ) );
   for ( i = 0; i < 10; ++i)
   {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;//在 C 语言中,数组名本身就是一个指向数组第一个元素的指针;因此,在这个程序中,
            //getRandom() 函数返回的是一个指向数组第一个元素的指针,而不是一个数组。
}
 
/* 要调用上面定义函数的主函数 */
int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();//声明并初始化了一个指向整数的指针 p,它的值是 getRandom() 函数的返回值。
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
 
   return 0;
}

十、函数指针

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

函数指针可以像一般函数一样,用于调用函数、传递参数。

(1)函数指针定义格式:

<类型说明符> (*<函数指针变量>)(<参数类型表>);

在这里插入图片描述

(2)函数指针变量的声明:

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型

以下实例声明了函数指针变量 p,指向函数 max:

#include <stdio.h>
 
int max(int x, int y)
{
    return x > y ? x : y;
}
 
int main(void)
{
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &可以省略
    int a, b, c, d;
 
    printf("请输入三个数字:");
    scanf("%d %d %d", & a, & b, & c);
 
    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c); 
 
    printf("最大的数字是: %d\n", d);
 
    return 0;
}

(3)函数指针的使用

····用函数指针调用函数

函数可以通过函数名调用,也可通过函数指针调用。
如果 fp=max,则对函数 max 的调用方式可以是:

  max(实参表)/* 通过函数名调用,建议使用 */
  (*max)(实参表)/* 通过函数名调用 */
  fp(实参表)/* 通过函数指针调用,建议使用 */
  (*fp)(实参表)/* 通过函数指针调用 */
#include <stdio.h>
int main( ) 
{  
	int max(int, int), min(int, int); /* 函数原型声明 */
	int (*fp)(int, int);  /* 定义函数指针 fp */
	int a, b, c, d;
	scanf("%d%d", &a, &b);
	fp=max;        /* fp 指向 max( ) 函数 */
	c=fp(a, b);      /* 通过 fp 调用 max( ) 函数 */
	fp=min;          /* fp 指向 min( ) 函数 */
	d=(*fp)(a, b);   /* 通过 fp 调用 min( )函数 */
	printf("max=%d\tmin=%d\n", c, d);
	return 0;
}
int max(int x,int y)  {  return(x>y?x:y);  }  
int min(int x,int y)  {  return(x<y?x:y);  }  
···函数指针作为函数参数(回调函数)

实现两个或多个函数之间的调用控制
eg1:

int main( )
{ int max(int,int),min(int,int),add(int,int);/*函数原型声明*/
  int process(int, int, int (*)(int,int));    /*函数原型声明 */
  int a,b ;
  	printf("Enter a and b:");
	scanf("%d%d", &a, &b);
	printf("max=%d\t", process(a, b, max));  
	printf("min=%d\t", process(a, b, min));   
	printf("sum=%d\n", process(a, b, sum)); 
	return 0;
}
int process(int x,int y, int (*fun)(int,int))
{    return fun(x,y);  }
int max(int x, int y)
{ return( x>y ? x :y ); }
int min(int x, int y)
{ return( x<y ? x :y ); }
int add(int x, int y)
{ return( x + y ); }

eg2:populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。

我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。

populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

实例

#include <stdlib.h>  
#include <stdio.h>
 
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言是一种广泛应用于软件开发的编程语言,具有简洁、高效、灵活等特点。为了更好地学习和掌握C语言,整理一份完整的笔记是非常有必要的。 首先,C语言的基础知识是理解和掌握该语言的关键。笔记中应包含C语言的基本语法,如变量的声明和定义、数据类型、运算符、控制语句等,这些是编写C程序的基础。 其次,C语言的函数也是非常重要的一部分。函数是C语言中的一种模块化的编程方式,可以提高代码的重用性和可维护性。在笔记中应包含函数的定义和调用、参数传递、函数返回值等内容,以及常用的C库函数的使用。 此外,C语言的数组和指针也是需要重点关注的内容。数组是一种存储多个相同类型数据的方式,而指针则是C语言中与内存地址相关的操作。在笔记中应包含数组的定义和初始化、多维数组的使用、指针的声明和运算等内容。 还有,C语言中的内存管理是需要特别注意的。动态内存分配是C语言的一个重要特性,可以根据需要在运行时分配和释放内存。笔记中应包括动态内存分配的函数,如malloc、free等,以及内存泄漏和内存溢出的问题的解决方法。 最后,C语言的文件操作也是需要掌握的一部分。文件操作是C语言中与磁盘文件进行读写的关键部分,可以实现数据的长久保存和共享。在笔记中应涵盖文件的打开和关闭、读取和写入、文件指针的操作等内容。 综上所述,C语言笔记的完整版应该包含C语言的基础知识、函数、数组和指针、内存管理以及文件操作等内容。通过阅读和整理笔记,可以更好地理解和应用C语言,提高程序编写的效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值