1.字符串函数
1.1strlen
size_t strlen ( const char * str );
1.11strlen函数的基本介绍
- strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )
- 参数所指向的字符串必须是由'\0'结尾的
- 函数的返回值类型为size_t,是无符号的
#include <stdio.h>
#include <string.h>
int main ()
{
char szInput[256];
printf ("Enter a sentence: ");
gets (szInput);
printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
return 0;
}
1.12strlen函数的模拟实现
通过分析我们只需计算出参数指向的字符串的'\0'前的字符数量即可,函数实现如下:
//模拟实现strlen
//方法1 计算器法
size_t my_strlen(const char* str) {
int count = 0;
while (*str!= '\0') {
count++;
str++;
}
return count;
}
----------------------------------------
//方法2 指针-指针
size_t my_strlen(const char* str) {
char* p = str;
while (*p) {
p++;
}
return p - str;
}
----------------------------------------
//方法3 递归方法
size_t my_strlen(const char* str) {
if (*str=='\0') {
return 0;
}
else {
return 1 + my_strlen(1 + str);
}
}
实现strlen函数我们可以使用上述三种不同的方法,根本思想就是找到'\0';
1.2strcmp
int strcmp ( const char * str1, const char * str2 );
1.21strcmp函数的基本介绍
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
那么字符串比较的规则是什么呢?
——我们在比较字符串大小时会逐个比较字符串的每一位字符,直到一方被比较完了(即有一方到了'\0'),当双方都已经比较到'\0'那么两个字符串的大小相等。
#include <stdio.h>
#include <string.h>
int main ()
{
char key[] = "apple";
char buffer[80];
do {
printf ("Guess my favorite fruit? ");
gets(buffer);
} while (strcmp (key,buffer) != 0);
puts ("Correct answer!");
return 0;
}
1.22strcmp函数的模拟实现
#include<assert.h>
int my_strcmp(const char* s1, const char* s2) {
assert(s1 && s2);
while (*s1 == *s2) {
if (*s1 == '\0') {
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)
return 1;
else
return -1;
}
1.3strcpy
char * strcpy ( char * destination, const char * source );
1.31strcpy函数的基本介绍
- 源字符串必须以 '\0' 结束
- 会将源字符串中的 '\0' 拷贝到目标空间
- 目标空间必须足够大,以确保能存放源字符串
- 目标空间必须可变
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]="Sample string";
char str2[40];
char str3[40];
strcpy (str2,str1);
strcpy (str3,"copy successful");
printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
return 0;
}
1.32strcpy函数的模拟实现
函数将src首地址一直到'\0'中间的元素从dest地址处开始拷贝,最后再将末尾的'\0'拷贝过来
char* my_strcpy(char* dest, char* src) {
char* ret = dest;
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
//最后要把'\0'放进去
*dest = *src;
return ret;
}
//简化版
#include<assert.h>
char* my_strcpy(char* dest, char* src) {
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest++ = *src++) {
;
}
最后要把'\0'放进去
*dest = *src;
return ret;
}
1.4strcat
char * strcat ( char * destination, const char * source );
1.41strcat函数的基本介绍
- 源字符串必须以 '\0' 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
#include <stdio.h>
#include <string.h>
int main ()
{
char str[80];
strcpy (str,"these ");
strcat (str,"strings ");
strcat (str,"are ");
strcat (str,"concatenated.");
puts (str);
return 0;
}
1.42strcat函数的模拟实现
我们先找到dest地址后的第一个'\0'作为拼接的首元素,将src中的全部元素(包括'\0')从改地址处依次向后拼接
//模拟实现
#include<assert.h>
char* my_strcat(char* dest, const char* src) {
char* ret = dest;
assert(dest);
assert(src);
while (*dest != '\0') {
dest++;
}
while (*dest++ = *src++) {
;
}
return ret;
}
//注意不要使用两个相同的字符串连接,因为会将'\0'的值改变,会一直持续不停
1.5strncpy/strncmp/strncat
char * strncpy ( char * destination, const char * source, size_t num );int strncmp ( const char * str1, const char * str2, size_t num );char * strncat ( char * destination, const char * source, size_t num );
1.51strn类函数的基本介绍
相较于前面的几种函数我们发现这种函数名称中间多了一个n的函数在参数上比他们多了一个size_t num,参数的含义是字符数(可以理解为字节数),这个参数相当于我们对其所做的限制条件,我们限制其对我们所规定的字符数进行相应的操作。
值得注意的一点是,strncpy如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
1.6strstr
const char * strstr ( const char * str1, const char * str2 ); char * strstr ( char * str1, const char * str2 );
1.61strstr函数的基本介绍
- strstr(s1,s2)找到s2在s1中第一次出现的位置
- 如果没找到则返回空指针
#include<stdio.h>
#include<stringh.h>
int main() {
char s1[] = "i love you but...";
char s2[] = "you";
char* ret = strstr(s1, s2);
if (ret != NULL)
printf("%s\n", ret);
else
printf("找不到\n");
return 0;
}
1.62strstr函数的模拟实现
//模拟实现
char* my_strstr(char* str1, char* str2) {
char* cp = str1;
char* s1 = cp;
char* s2 = str2;
while (*cp) {
s1 = cp;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
if (*s2 == '\0')
return cp;
cp++;
}
return NULL;
}
在模拟实现的时候我们定义了一个标志变量cp来保证逐渐向后比较字符时str1的地址不被改变
比较s1和s2的所指向的值两者不相等则cp++,当找到第一个相同的元素后进入第二个while循环,依次向后比较直到s2指到'\0'了,则找到子字符串,若s1到'\0'而s2还没比较完那么str1当中没有我们所找的子字符串。
1.7strtok
char * strtok ( char * str, const char * delimiters );
1.71strtok函数的基本介绍
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
#printf<stdio.h>
#include<string.h>
int main() {
char str[] = "IsLiuzy@qq.com#6628";
char cpy[50];
strcpy(cpy, str);
char sep[] = "@.#";
char* ret = NULL;
for (ret = strtok(str, sep); ret != NULL; ret = strtok(NULL, sep)) {
printf("%s\n",ret);
}
return 0;
}
1.8strerror
char * strerror ( int errnum );
1.81strerror函数的基本介绍
- 传入错误码errnum返回错误信息(errno是一个全局变量,系统会将错误码保存在里面,每个错误码都应一种错误信息)
//strerror返回错误码对应的错误信息
int main() {
/*int i = 0;
for (i = 0; i < 10; i++) {
printf("%d:%s\n",i, strerror(i));
}*/
//C语言操作文件的步骤
//1.打开文件
//2.读/写文件
//3.关闭文件
FILE* pf = fopen("data.txt","r");
if (pf == NULL) {
printf("%s\n", strerror(errno));
//errno是一个全局变量,系统会将错误码保存在里面
perror("fopen");
//perror可以直接帮我们打印出错误信息
return 1;
}
//....
fclose(pf);
return 0;
}
1.9字符函数
1.91字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v' |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F |
islower | 小写字母a~z |
idupper | 大写字母A~Z |
isalpha | 字母a~z或A~Z |
isalnum | 字母或者数字,a~z,A~Z,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
1.字符转换函数
int tolower ( int c );
int toupper ( int c );
#include<ctype.h>
int main() {
/*printf("%d\n", isupper('S'));
printf("%d\n", isdigit('2'));
printf("%c\n", tolower('A'));
printf("%c\n", toupper('s'));*/
//读取一段字符串将字符串中的大写字母改为小写
char str[20];
gets(str);
//gets可以读取空格
char* p = str;
while (*p) {
if (isupper(*p)) {
*p = tolower(*p);//*p=*p+32
}
p++;
}
printf("%s\n", str);
return 0;
}
2.内存函数
2.1memcpy
void * memcpy ( void * destination, const void * source, size_t num );
2.11memcpy函数的基本介绍
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
- 这个函数在遇到 '\0' 的时候并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的
- 如果num不是规定元素类型的整数倍时我们可以分析拷贝的地址
#include<string.h>
#include<stdio.h>
int main() {
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
//将四个整形元素拷贝到arr2中
memcpy(arr2, arr1, 40);
for (int i = 0; i < 10; i++) {
printf("%d ", arr2[i]);
}
return 0;
}
2.12memcpy的模拟实现
我们将目的地和起始地址强制类型转换为(char*)类型这样使用字节数num时会更方便。
//模拟实现memcpy
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num) {
assert(dest && src);
void* ret = dest;
while (num--) {
*(char*)ret = *(char*)src;
ret = (char*)ret + 1;
src = (char*)src + 1;
}
return dest;
}
int main() {
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memcpy(arr2, arr1, 40);
//my_memcpy(arr1+3, arr1, 40);
//memcpy函数是用来处理不重叠的内容拷贝
//可以考虑使用memmove
for (int i = 0; i < 10; i++) {
printf("%d ", arr2[i]);
}
return 0;
}
2.2memmove
void * memmove ( void * destination, const void * source, size_t num );
2.21memmove函数的基本介绍
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理
2.22memmove函数的模拟实现
memmove能够实现重叠拷贝的原因是,memmove在拷贝同一个字符串时会将两者的地址进行比较,来选择对应的拷贝方式(从前->后\从后->前)
当dest<=src,我们可以直接从首地址开始拷贝(标红位置为第一处开始拷贝的地址)
若dest>=src,为了避免将要拷贝的数给覆盖我们选择从后面开始拷贝
//模拟实现memmove
void* my_memmove(void* dest, const void* src, size_t num) {
assert(dest && src);
void* ret = dest;
if (dest < src) {
while (num--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else {
while (num--) {
*((char*)dest+num) = *((char*)src+num);
}
}
return ret;
}
int main() {
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memmove(arr1, arr1+2, 20);
for (int i = 0; i < 10; i++) {
printf("%d ", arr1[i]);
}
return 0;
}
2.3memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
2.31memcmp函数的基本介绍
比较从ptr1和ptr2指针开始的num个字节 返回值如下:
Returns an integral value indicating the relationship between the content of the memory blocks:
return value | indicates |
---|---|
<0 | the first byte that does not match in both memory blocks has a lower value in ptr1 than in ptr2 (if evaluated as unsigned char values) |
0 | the contents of both memory blocks are equal |
>0 | the first byte that does not match in both memory blocks has a greater value in ptr1 than in ptr2 (if evaluated as unsigned char values) |
/* memcmp example */
#include <stdio.h>
#include <string.h>
int main ()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n=memcmp ( buffer1, buffer2, sizeof(buffer1) );
if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2);
else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2);
else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2);
return 0;
}
2.32memcmp函数的模拟实现
//模拟实现memecmp
int my_memcmp(const void* ptr1, const void* ptr2, size_t num) {
int i=0;
for (i = 0; i < num; i++) {
if (*(char*)ptr1 > *(char*)ptr2)
return 1;
else if (*(char*)ptr1 < *(char*)ptr2)
return -1;
ptr1 = (char*)ptr1+1;
ptr2 = (char*)ptr2+1;
}
return 0;
}
int main() {
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 1,2,3 };
int ret = my_memcmp(arr1, arr2, 12);
printf("%d\n", ret);
return 0;
}
2.4memset
void * memset ( void * ptr, int value, size_t num );
2.41memset函数的基本介绍
- 将指定地址处之后num个字节的值设置为value
/* memset example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}
2.42memset函数的模拟实现
//模拟实现memset
void* my_memset(void* src, int value, size_t num) {
int i = 0;
void* ret = src;
for (i = 0; i < num; i++) {
*(char*)src = value + '0';
src = (char*)src + 1;
}
return src;
}
int main() {
char str[] = "my love";
my_memset(str+1, 3, 1);
printf("%s\n", str);
/*int arr[10] = { 0 };
memset(arr, 1, 10);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}*/
//memset是将每个字节设置为1而不是值
//1 ---01 00 00 00 memset 01 01 01 01
return 0;
}
总结
内存函数和类似的字符串函数相比只是多增加了参数 size_t num 这使得我们使用函数更有针对性。