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

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

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

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.xdnf.cn/news/16667.html

相关文章:

  • JavaWeb 入门:CSS 基础与实战详解(Java 开发者视角)
  • DataParallel (DP) DistributedDataParallel (DDP)
  • JavaWeb学习打卡18(JDBC案例详解)
  • [leetcode] 电话号码的排列组合
  • CSRF漏洞原理
  • CentOS7 安装和配置教程
  • USRP X410 X440 5G及未来通信技术的非地面网络(NTN)
  • Matplotlib(三)- 图表辅助元素
  • 经典算法题解析:从思路到实现,掌握核心编程思维
  • 天学网面试总结 —— 前端开发岗
  • Go 语言-->指针
  • 【2025/07/28】GitHub 今日热门项目
  • 【服务器知识】nginx配置ipv6支持
  • 大模型的开发应用(十九):AIGC基础
  • 【Spring WebFlux】 三、响应式流规范与实战
  • Java 笔记 serialVersionUID
  • ADB+Python控制(有线/无线) Scrcpy+按键映射(推荐)
  • 服务器查日志太慢,试试grep组合拳
  • 时序数据库选型指南:工业大数据场景下基于Apache IoTDB技术价值与实践路径
  • 5 分钟上手 Firecrawl
  • 【办公类-109-01】20250728托小班新生挂牌(学号姓名)
  • API产品升级丨全知科技发布「知影-API风险监测平台」:以AI重构企业数据接口安全治理新范式
  • 企业级日志分析系统ELK
  • Pycaita二次开发基础代码解析:点距测量、对象层级关系与选择机制深度剖析
  • 基于DeepSeek大模型和STM32的矿井“围压-温度-开采扰动“三位一体智能监测系统设计
  • 边缘计算+前端实时性:本地化数据处理在设备监控中的响应优化实践
  • vue element 封装表单
  • STM32时钟源
  • GaussDB as的用法
  • 【氮化镓】GaN同质外延p-i-n二极管中星形与三角形扩展表面缺陷的电子特性