嵌入式第十四课!!!指针在字符数组的应用与数组指针
字符数组指针作为函数参数
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 的初始地址。
以上就是今天和大家分享的内容!!!感谢你的阅读!!!如果有遗漏和错误,欢迎各位大佬在评论区进行批评和指正!!!