当前位置: 首页 > news >正文

嵌入式第十四课!!!指针在字符数组的应用与数组指针

字符数组指针作为函数参数

1.Strcpy函数

void Strcpy(char *dest , char *src)
{while(*src != 0){*dest  = *src;++dest;++src;}*dest = 0;
}

在调用这个函数的时候:

int main(void)
{char s1[] = "Hello";Strcpy ("    world" , s1);return 0;
}

如果这样调用,将字符串常量作为目标字符数组是不被允许的:这是因为字符串常量存储在字符串常量区,是不能被随意修改的,而 s1 是将“Hello”复制到栈区,所以可以对s1 进行修改, 如:

int main(void)
{char s1[] = "Hello";Strcpy (s1 , " world!" );return 0;
}

在我们之前学习的部分,还有一种变量是无法随意修改的,就是const变量

const int i = 100;

如果想对 i 的值进行改变,不能通过直接访问的方式修改 , 如:

const int i = 100;

i = 1000;

系统将会发生报错,但是我们可以通过指针来间接访问 i 来修改 i 的值:

const int i = 100;

int *p = &i;

*p = 1000;

这样就可以改变 i 的值;如果我们在指针变量 p 前添加 const 是不是就不能改变 p 指向的地址呢?

int i = 100;

const int *p = &i ;

其实不是这样的,p 还可以改变它指向的变量地址,但是不能通过指针修改*p 的值 ,即 i 的值

int i = 100;

const int *p = NULL;

p = &i;

\\*p = 1000; 不能修改*p 的值

2.puts函数

为了防止系统崩溃,即在传参过程中修改字符串常量区的字符串,在传参时,给形式参数添加(const ,如 从const char *s),可防止在调用函数中程序语句修改字符串;这时如果强行修改字符串将会在编译时报错。

故为了保证传参正确性,凡是不能改的参数都可以添加 const 。

void Puts(const char* s)
{while(*s){putchar(*s);++s;}}

3.Strcat函数

void Strcat (char *dest , const char *src)
{while(*dest){++dest;}while (*src){*dest = *src;++dest;++src;}*dest = '\0';
}

和在字符数组章节讲过的类似,先遍历dest的有效字符,再从dest 的' \0 '的位置再开始遍历src的有效字符。(循环while的条件非0即为真)

4.Strcmp函数

int Strcmp(const char *s1, const char *s2)
{while(*s1 == *s2 && *s1 && *s2){++s1;++s2;}return *s1 - *s2;
}

逻辑与之前讲过的类似,这里可以注意的一个细节是:添加了const形式参数的函数在调用时都可以传字符串常量。

同一个字符串常量只会在字符串常量区存储一份;这里要注意的是,我们说的“同一个”,指的不仅是值相同,地址也要相同。

5.Strncpy函数

这个函数在调用时,需要传递三个参数,分别是目标函数、原函数、复制个数n:

void Strncpy(char *dest, const char *src,int n)
{while(*src && n--){*dest = *src;++dest;++src;}*dest = '\0';
}

函数的作用是可以将src的前 n 个字符拷贝到 dest 里,需要注意的是就算n输的再多,也只会复制strlen( src) 个有效字符。

如果在传递参数的时候,实际参数的数组是short 型 、int 型等其他数据类型怎么办呢?

如果Strncpy函数是这么定义的:

void Strncpy(int *dest , int * src ,int n)

如果传递的实际参数是short型数组,将会导致越界,导致栈区崩溃;

但是如果形式参数是char* 型,short型数组每个元素的两个字节分别存入char数组中,在编译时会产生数组类型不匹配的警告,最终实际系统是可以正常运行的。

为了防止数组类型不匹配的问题,我们引入一个指针:

void *  万能指针

和基类型指针的用法类似,这是一个万能指针;只要是地址都可以存,但是不能做指针运算,因为没有void型数据,不能进行运算,系统会报错 。

我们一般搭配强转来进行使用:在存放地址后,可以在程序中通过强转使其成为需要的类型

利用这种方法,Strncpy函数会成为一个可以传递不同基类型数组的函数,它有一个新的名字:

6.Memcpy函数

它的定义为:

void Memcpy(void *dest , const void *src , int n)
{char *p = (char *)dest;char *q = (char *)src;while(n--){*p = *q;++p;++q;}
}

如果我们要传递的实参可能是任何数据类型,可以使用万能指针 void *

7.Strncat函数

void Strncat(char *dest , const char * src,int n)
{while (*dest){++dest;}while(*src && n--){*dest = *src;++dest;++src;}*dest = 0;
}

这个函数有点类似与Strncpy函数的用法,它是将 src 数组的前 n 个字符连接到目标数组dest后。

8.Strncmp函数

int Strncmp(const char *s1 , const char *s2 , int n)
{while(--n && *s1 == *s2 && *s1 && *s2 ){++s1;++s2;}return *s1 - *s2;
}

这个函数可以在前 n 个字符的范围里比较两个字符串的大小,通过应用这个函数,我们可以在文本里查找特定的单词:

int Strncmp(const char *s1 , const char *s2 , int n)
{while(--n && *s1 == *s2 && *s1 && *s2 ){++s1;++s2;}return *s1 - *s2;
}
int Find(const char*s, const char*sub , unsigned long lens1 , unsigned long lens2)
{int i;for(i = 0 ; i <= lens1 - lens2 ; i++){if(Strncmp(s + i , sub ,lens2) == 0){break;}}if(i > lens1 - lens2 ){return -1;}else{return i;}
}
int main(void)
{char s1[100] = "Hello";char s2[100] = "He";int ret = Find(s1 , s2 , strlen(s1) , strlen(s2));if(ret != -1){puts("found!");}else{puts("not found!");}return 0;
}

运行结果为:

即这个单词在字符串的第0个位置。

数组指针

定义一个数组:

int a [10];

定义指针 int *p是不可以存储数组的地址&a的,要想存储数组 a 的地址要使用数组指针:

int (*p)[10] = &a;  

在数组指针里,其基类型是 int [10] ,这是一个指向数组的指针。

如果将指针定义成这样:

int *p[10] = &a;

不给*p 单独添加括号,系统会认为定义了10 个连续的指针数组,而不是一个指向数组的指针变量。

辨析一下二者的区别:

int (*p)[10] ;

sizeof(int (*p)[10] )  = 8;

 int *p[10] ;

sizeof(int *p[10] ) = 80;

故 p 与 p + n之间相差了 n * (基类型所占字节 * 数组长度)个字节,所以数组指针常在二维数组里应用。

指针在二维数组的应用

定义一个二维数组:

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

此时数组名 a 代表的是a[0]行一维数组的首地址,此时定义一个数组指针:

int (*p )[4] ;

p = a;

*a 实际上是*(a + 0 ),也就是a[0],也就是a[0][0]元素的地址;

*a + 1 先算指针运算符*a ,从a[0][0]的地址向后顺延4个字节,也就是下一个元素;

按照该逻辑:

a[i][j] = *(*(a + i) + j)

故二维数组的行数rows和列数cols可以这么算:

int rows = sizeof(a) / sizeof(*a);

int cols = sizeof(*a) / sizeof(**a);

辨析一下

*((int *)(p + 3) - 5 )

p是a的数组指针,先指向第四行数组首元素的地址,此时是一个野指针,再往后顺延5个元素,即指向a[1][4] 的元素。

在二维数组作为函数参数传递时,形参时指向一维数组的指针,即数组指针,举几个应用的例子供大家参考:

应用实例

1.打印二维数组

void printArray2D(int (*a)[4] , int rows)
{int i, j ;for(i = 0  ; i < rows ; ++i){for(j = 0 ; j < 4 ; ++j){printf(" %2d " , *(*(a + i) + j));}puts("");}
}
int main(void)
{int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int rows = sizeof (a) / sizeof (*a);printArray2D(a,rows);
}

2.计算二维数组所有元素之和

int sumOfArray2D(int (*a)[4] , int rows)
{int i,j;int sum = 0; for(i = 0 ; i < rows ; ++i){for(j = 0 ; j < 4 ; ++j){sum += *(*(a + i) + j); }}return sum;
}

3.对二维数组里每行元素进行逆序

void swap(int *a , int *b)
{int t = *a;*a = *b;*b = t;
}
void reverse(int *begin , int *end)
{while(begin < end){swap (begin , end);++begin;--end;}
}
void reverseOfArray2D(int (*a)[4] , int rows)
{int i,j;for(i = 0 ; i < rows ; ++i){reverse(*(a + i), * (a + i) + 3);}
}
int main(void)
{int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int rows = sizeof (a) / sizeof (*a);reverseOfArray2D(a,rows);printArray2D(a,rows);
}

在定义二维数组函数的时候,我们调用了昨天练习过的一维数组递归逆序函数。需要注意的是,只需要遍历二维数组的行就可以了,每一行都是一个一维数组,直接调用reverse函数,不需要对其再进行列数遍历。

指针函数

定义

指针函数即是返回值为指针的函数;在调用函数时,返回值是局部变量的地址时会发生错误,这是因为局部变量在返回主函数时,空间已被销毁,此时返回值是一个野指针。

所以我们可以设返回值为全局变量、静态变量(static)和当初从主函数传递进去的地址(形式参数可以是指针)。

应用

#include<stdio.h>
char *Strcpy(char *dest , const char *src)
{char *ret = dest;while (*src){*dest = *src;++dest;++src;}*dest = 0;return ret;
}
char *Strcat(char *dest , const char *src)
{char *ret = dest;while(*dest){++dest;}while (*src){*dest = *src;++dest;++src;}*dest = 0;return ret;
}
int main(void)
{char s1[100] = "Hello";char s2[100] = " World!";puts(Strcat(s1,s2));puts(Strcpy(s1 + 1,s2));
}

在这里需要注意的是,如果在调用函数的末尾直接return dest的话,会直接打印出地址最后的字符,即打印’\0‘;故在进行遍历之前先设置一个返回指针*ret存储 dest 的初始地址。

以上就是今天和大家分享的内容!!!感谢你的阅读!!!如果有遗漏和错误,欢迎各位大佬在评论区进行批评和指正!!!

http://www.dtcms.com/a/305075.html

相关文章:

  • 【Lua】题目小练3
  • 13、select_points_object_model_3d解析
  • Excel制作滑珠图、哑铃图
  • HCIP--MGRE综合实验
  • 从0到1学PHP(五):PHP 数组:高效存储与处理数据
  • C#_ArrayList动态数组
  • 【C#获取高精度时间】
  • 同一个局域网段,如何实现所有用户都可以访问本地mysql数据库?
  • 理解Transformer解码器
  • 【三桥君】企业级AI应用需要系统工程支撑,如何通过MCP大模型架构实现全链路实战解构?
  • 1 RAG三问
  • Javaweb————HTTP请求头属性讲解
  • ​第七篇:Python数据库编程与ORM实践
  • Python的‌列表推导式‌
  • 065_线程创建方式(继承Thread / 实现Runnable Callable)
  • Hyperchain账本数据存储机制详解
  • SpringCloud之Gateway
  • ORACLE的用户维护与权限操作
  • 车载刷写架构 --- 整车刷写中为何增加了ECU 队列刷写策略?
  • 激光雷达/相机一体机 时间同步和空间标定(1)
  • [leetcode] 电话号码的排列组合
  • elememtor 添加分页功能
  • GaussDB 约束的语法
  • 互联网前沿新技术
  • 老年护理实训室建设方案:打造安全、规范、高效的实践教学核心平台
  • win10更新异常,导致Microsoft Store 无法正常启用,无法正常安装exe应用程序。
  • Mqttnet的MqttClientTlsOptions.CertificateValidationHandler详解
  • yolov11的简单实例
  • Python爬虫03_Requests破解百度翻译
  • 7、如何管理昵称重复?