嵌入式学习第三周(指针 下)

一、指针数组和数组指针

        1、指针数组

数组的每一个元素都是指针变量  ---本质:是一个数组(每个元素都是指针类型的数组)

形式: 数据类型 *指针数组名[数组长度];

注意:所有元素都必须是同种存储类型和指向相同数据类型,指针数组通常和指针的指针双重指针等价。 

如: int r,b,c,d,e *a[5]={&r,&b,&c,&d,&e};

指针数组主要用于处理若干个字符串。“Sunday”代表的是首地址

如:char * name[] = {“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”};

         2.数组指针

  •   指向由N个元素组成的维数组 --本质:是一个指针
  •   赋值方式:int a[5][10]; int (*pa)[10]=a ;或int (*pa)[10]  pa=a;

注意:()不能缺少。n必须与二维数组a[i][j]的j相同,数组指针通常和二维数组名等价。

访问方式:p[i][j] 或 *(p+i)[j] 或 *(*(p+i)+j)

 //下标替换方法:p[i][j]

 //替换行号:*(p+i)[j]

 //函数指针常见方法:*(*(p+i)+j)

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

  • 指针数组,如:int *p[3]
    指针数组:指针数组可以说成是指针的数组,首先这个变量是一个数组。
    其次,指针修饰这个数组,意思是说这个数组的所有元素都是指针类型。
    在 32 位系统中,指针占四个字节
  • 数组指针,如:int (*p)[3]
    数组指针:数组指针可以说成是数组的指针,首先这个变量是一个指针。
    其次,数组修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址

二、指针与字符串 

字符指针:指向 char 型的指针变量 char *pa;

(1)让字符指针指向存放字符串的数组: char a[]=”abcd”;char *pa=a;

(2)让字符指针指向字符串常量:       char *str;  str=”abcdef”;

(3)定义字符指针时指向字符常量:     char *str=”abcdefg”;

 1.字符串指针数组问题

char str[10] = "hello"; // 定义一个长度为10的字符数组,存储字符串"hello"
char *str = "hello";// 使用 字符指针表示法:

在C语言中,char *str = "hello"; 和 char str[] = "hello"; 都可以用来表示字符串,但它们之间有一些区别:

  • 存储方式:
    char *str = "hello";   str 是一个指针,它指向一个字符串字面量(string literal)“hello”。字符串字面量通常存储在程序的只读内存区域(如常量区),因此不能修改。str 变量本身存储在栈上。
    char str[] = "hello";str 是一个字符数组,它的内容是字符串 “hello”。字符串的字符存储在栈上,因此可以修改。str 变量本身也存储在栈上。
  • 可修改性:
    char *str = "hello";:由于 str 指向的是只读内存区域,所以不能修改字符串内容。例如,尝试修改字符串的操作(如 str[0] = 'H';)可能会导致程序崩溃。
    char str[] = "hello";:由于 str 是一个字符数组,存储在栈上,所以可以修改字符串内容。例如,可以执行 str[0] = 'H'; 操作。
  • sizeof操作符的结果:
    char *str = "hello";:使用 sizeof(str) 时,将返回指针的大小(通常为4字节或8字节,取决于操作系统和编译器)。
    char str[] = "hello";:使用 sizeof(str) 时,将返回字符数组的大小,即6字节(包括字符串结尾的空字符’\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;
}

结果:
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali

 解析:

由于 字符串 是以字符数组的形式存储的,以空字符('\0')作为结束标志。所以当定义一个字符串指针数组 char *names[] 时,数组中的每个元素都是一个指向字符数组(字符串)的指针
当 访问 names[i] 时,实际上是获取了指向字符串的指针。然而,C 语言中的字符串处理函数(如 printf)通常会自动处理指针,它们会根据指针所指向的地址,逐个字符地读取字符串,直到遇到空字符('\0')为止。因此,当使用这些函数时,它们会显示字符串的内容,而不是指针的地址

实例:

#include <stdio.h>

void main()
{
    char *names[] = {"Zara Ali","ASD"};

    char **m;
    m = names;
    // m = names[0];
    printf("Value of names = %p\n", names);
    printf("Value of m = %p\n", m );
    printf("Value of *m = %s\n", *m );

    char *n;
    n = *names;
    // m = names[0];
    printf("Value of n = %p\n", n );
    printf("Value of n = %s\n", n );
    printf("Value of *n = %c\n", *n ); 
}

输出结果:
Value of names = 0061FF10
Value of m = 0061FF10
Value of *m = Zara Ali
Value of n = 00405064
Value of n = Zara Ali
Value of *n = Z

分析:

 char *names[] = {"Zara Ali", "ASD"}; 定义了一个指针数组数组中的每个元素都是一个指向字符数组(字符串)的指针。当使用 char *m; 时,定义了一个指向字符的指针。所以,m 的类型是 char *
   而 names 是一个指针数组,它的类型是 char **,因为它是一个指向指针的指针。而数组名在大多数情况下会被解释为指向数组第一个元素的指针,所以 不能将 names 直接赋值给 m ,因为类型不同。
  但是,你可以将 names 数组中的某个元素(即一个指向字符串的指针)赋值给 m,例如:m = names[0]; // 或者 m = *names;
这样,m 就指向了字符串 “Zara Ali”。
  当使用 n = *names 时,相当于把字符串指针数组的第一个元素给了n,即"Zara Ali",当在使用 *n 时,就指向了字符串的第一个元素即字符:Z

三、双重指针 

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

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 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 = 0x7ffee2d5e8d8
*Pt1 = 100
Pt2 = 0x7ffee2d5e8d0
**Pt2 = 100

四、野指针和空指针

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

    int a = 100;
	int *p;
	p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义

	p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义

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

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

 int *p = NULL;

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

#define NULL    ((void *)0) 

 五、万能指针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;
// *p1 = 111 // err
p1 = &b; // ok

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

在编辑程序时,指针作为函数参数,如果不想修改指针对应内存空间的值,需要使用const修饰指针数据类型

七、指针和函数 

   1、指针作为函数参数 

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

#include <stdio.h>
#include <time.h>
 
void getSeconds(unsigned long *par)
{
   /* 获取当前的秒数 */
   *par = time( NULL );
   return;
}

int main ()
{
   unsigned long sec;
   getSeconds( &sec );
   /* 输出实际值 */
   printf("Number of seconds: %ld\n", sec );
   return 0;
}

结果:
Number of seconds :1294450468

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

#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

2、指针函数 

  • 指针函数返回值的类型为指针类型的函数,本质是一个函数。
  • 形式:函数类型 *函数名(参数表)
  • 只要返回类型是指针类型的函数就是指针函数。 

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;
}
 
/* 要调用上面定义函数的主函数 */
int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
 
   return 0;
}

结果:
1523198053
1187214107
1108300978
430494959
1421301276
930971084
123250484
106932140
1604461820
149169022
*(p + [0]) : 1523198053
*(p + [1]) : 1187214107
*(p + [2]) : 1108300978
*(p + [3]) : 430494959
*(p + [4]) : 1421301276
*(p + [5]) : 930971084
*(p + [6]) : 123250484
*(p + [7]) : 106932140
*(p + [8]) : 1604461820
*(p + [9]) : 149169022

3、函数指针

函数指针: 指向函数的指针 --本质是指针

形式: 存储类型(数据类型) (* 函数指针变量名)(参数表);   int (*p)(int x);

调用: (* 函数指针变量名) (参数表);

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

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

例:int fun(int x);//声明一个函数

int (*p)(int x);//声明一个函数指针变量

*p=&fun;或者p=fun;  //这里不用带括号

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

结果如下:
请输入三个数字:1 2 3
最大的数字是: 3

八、动态内存分配与指向它的指针变量

1.为什么存在动态内存分配?

int a = 20;//在栈空间开辟四个字节
char arr[10] = { 0 };//在栈空间开辟10字节的连续空间

这种开辟空间的方式的缺点:

  1. 空间开辟大小是固定的
  2. 数组在申明的时候,必须指定数组的长度,他所需要的内存在编译时分配 
  3. 但是对于空间的需求,不仅需要如此,有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就需要动态内存开辟了。

2、动态内存函数介绍 

(1)、malloc和free函数的介绍

动态开辟函数头文件均为 stdlib.h

void* malloc(size_t size)  申请内存

A、功能介绍:
向内存申请一块连续可用的空间,并返回指向这块空间的指针
B、参数介绍:
void *:因为开辟空间的类型不确定。如果开辟成功,则返回第一个字节地址(一个指向开辟好空间的指针),如果开辟失败,则返回一个NULL指针,所以是否开辟成功,最好要做一个检查,避免出现非法访问内存的问题
size:要开辟的字节数。如果他是0,malloc的行为是标准为定义的,取决于编译器,一般是不允许这么写的!

void free(void* ptr)  释放空间

 A、功能介绍:
因为向内存动态申请了空间,你就应该对申请来的动态内存释放和回收,归还给操作系用,这就用到free函数了
B、参数介绍:
ptr:指向动态内存开辟的指针
C、注意:
1.如果参数ptr指向的空间不是动态内存开辟的,那么free的因为是未定义的(就是不允许这样的意思)
2.如果参数ptr是NULL指针,free函数什么都不做

例子如下:

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

int main()
{
	//比如我想开辟10个int类型的空间,如何动态开辟?
	int* ptr = (int*)malloc(10 * sizeof(int));
	//开辟完要判断malloc是否开辟成功
	//防止访问NULL,非法访问内存
	if (NULL != ptr)
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			//其实你开辟完一段连续的内存空间
			//ptr就可以当成数组名来用了
			*(ptr + i) = 0;
			//等价于ptr【i】= 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;
	return 0;
}

上段代码有两个疑问:

一、为什么malloc要强制类型转换?

int* ptr = (int*)malloc(10 * sizeof(int));
因为mallo函数本来返回void*,你要开辟的为int类型的,所以你malloc就转为int的,这样int类型的指针解引用就可以访问4个字节,它的意义主要是在这里!

二、为什么要置ptr=NULL? 

因为ptr指针指向的动态开辟的空间free函数已经释放了,ptr指针如果不置成NULL,还会指向那块空间,(ptr依然保存申请空间的地址)但是如果你后面又不小心访问了ptr所指向那块申请来的空间,它已经不属于你了,这是非法访问内存的行为,所以要把ptr置成NULL,防止你因为访问ptr所指向的那块动态开辟的空间而出错。

(2)calloc函数介绍 

void* calloc(size_t num, size_t size)

A、功能介绍:
同样适用于动态内存开辟空间,与malloc差不多,只是有小差别而已
B、参数介绍:
num:要开辟的元素个数
size:一个元素多大,单位:字节
C、和malloc的区别:
1、参数不同,malloc只有一个参数而已
2、calloc会初始化内存每个元素为0,而malloc不会
相同在于都在内存的堆区上申请一块空间
calloc 和 malloc 的区别看下面的代码就懂了!
 

在这里插入图片描述

在这里插入图片描述

 (3)realloc函数的介绍

void* realloc (void*ptr , size_t size)

A、功能介绍:
        有时候我们会发现过去申请的空间太小或者过大了,而为了合理的使用内存,我们会对内存大小做一定的调整,realloc就可以做到,他让动态内存管理更加灵活。这个函数会在调整原内存空间大小的基础上,将原来内存中的数据移动到新的空间。
B、参数介绍:
1、ptr:要调整的内存地址
2、size:你要调整之后的新大小
3、返回void星:调整之后的内存起始地址
C、realloc调整空间存在的三种情况:
 

在这里插入图片描述

在这里插入图片描述

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		//开辟失败perror函数会返回错误信息是什么
		perror("main");
		return 0;
	}
	//如果此时我想要20个int字节的空间,
	//不只是10个字节空间,就要用realloc函数
	int* ptr = (int*)realloc(p, 20 * sizeof(int));
	if (ptr != NULL)
	{
	//开辟成功才能使p=ptr,因为如果失败它返回NULL
	//p接收了NULL,它连原来的空间都会丢失!
		p = ptr;
	}
	free(p);
	p = NULL;
	return 0;
}

九、指针数组做为main函数的形参

int main(int argc, char *argv [ ]);

  • main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型

  • argv是命令行参数的字符串数组

  • argc代表命令行参数的数量,程序名字本身算一个参数

#include <stdio.h>

//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{

	//指针数组,它是数组,每个元素都是指针
	char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
	int i = 0;

	printf("argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}
	return 0;
}

总结:

  1. 指针的用法太多了,通过访问内存地址可以做很多事
  2. 又是励志的一天,将指针摸透,配合后面的只是应用起来,加油!工程师
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值