最近身体很不给力,很乏累!白天没精神,晚上睡不着,心情还很烦躁。看书都有点看不下去的样子,到了C语言最难掌握的部分了,数组、指针、字符串。硬着头皮看书总结吧。
一、字符串
1、字符串介绍
字符串是以空字符(\0)结尾的char数组,例如:
char ar[20] = "hello world";
2、定义字符串
字符常量,又称字符串文字,是指位于一对双引号中的任何字符。双引号里的字符加上编译器自动提供的结束标志(\0)字符,作为一个字符串被存储在内存里。
#include <stdio.h>
int main (void)
{
char ar1[50] = "hello" " " "world""!";
char ar2[50] = "hello world!";
printf ("%s\n", ar1);
printf ("%s\n", ar2);
printf ("\"We are family!\"\n");
return 0;
}
输出结果:
hello world!
hello world!
"We are family!"
3、字符串初始化
定义一个字符串数组时,必须让编译器知道它需要多大空间。
方法一,指定一个足够大的数组来容字符串
char ar[20] = "hello world";
char ar[2+5]; /*数组大小也可以是常量表达式*/
指定数组大小时,一定要确保数组元素数比字符串长度至少多1(多出来的1个元素用于容纳空字符),未被使用的元素均被自动初始化为0。这里的0是char形式的空字符,不是数字字符0.
方法二,让编译器决定数组大小
char ar[] = "hello world";
char ar[] = {'h' ,'e' ,'l' ,'l' ,'o' ,' ' ,'w' ,'o' ,'r' ,'l' ,'d' ,'\0'};
注意:标示结束的空字符。如果没有它,得到的就只是一个字符数组而不是一个字符串。
注意区分:数字字符 0,空字符 \0,空格 ' ',空指针 NULL。
方法三,指针初始化字符串
char * str = "hello world";
说明:数组和指针初始化字符串的区别
数组初始化是从静态存储区把一个字符串复制给数组,而指针初始化只是复制字符串的地址。其主要区别在于数组名ar是一个常量,而指针str则是一个变量。如,只有指针变量可以进行 str++。数组的元素是变量(除非声明数组时带有关键字const),但是数组名不是变量。如,*(ar + 2) = 'm'; 是可以的。
扩展,区分单个字符和字符串
用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符子啊编译器采用的字符集中的序列值。因此,对于采用 ASCII 字符集的编译器而言,'a' 的含义与 0141(八进制)或者97(十进制)严格一致。
用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符 '\0' 初始化。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void)
{
char *str1 = "abcde";
char str2[] = "abcde";
char str3[8] = "abcde";
char str4[] = {'a', 'b', 'c', 'd', 'e'};
char *p1 = malloc (20);
printf ("sizeof (str1) = %d, strlen (str1) = %d\n", sizeof (str1), strlen (str1));
printf ("sizeof (*str1) = %d, strlen (str1) = %d\n", sizeof (*str1), strlen (str1));
printf ("sizeof (str2) = %d, strlen (str2) = %d\n", sizeof (str2), strlen (str2));
printf ("sizeof (str3) = %d, strlen (str3) = %d\n", sizeof (str3), strlen (str3));
printf ("sizeof (str4) = %d, strlen (str4) = %d\n", sizeof (str4), strlen (str4));
printf ("sizeof (p1) = %d, sizeof (*p1) = %d\n", sizeof (p1), sizeof (*p1));
printf ("sizeof (malloc(20)) = %d\n", sizeof (malloc (20)));
return 0;
}
输出结果:
sizeof (str1) = 4, strlen (str1) = 5
sizeof (*str1) = 1, strlen (str1) = 5
sizeof (str2) = 6, strlen (str2) = 5
sizeof (str3) = 8, strlen (str3) = 5
sizeof (str4) = 5, strlen (str4) = 5
sizeof (p1) = 4, sizeof (*p1) = 1
sizeof (malloc(20)) = 4
根据上面的例子,可知 str1、str2以空字符(\0)结尾是字符串。而 str4 不是字符串。
如果两者混用,那么编译器的类型检查功能将会检测到这样的错误:
#include <stdio.h>
#include <string.h>
int main (void)
{
char *str = '2';
return 0;
}
输出结果:
警告: 初始化时将整数赋给指针,未作类型转换 [默认启用]
#include <stdio.h>
#include <string.h>
int main (void)
{
printf ("1111111\n");
printf ('\n');
printf ("2222222\n");
return 0;
}
输出结果:
test.c: 在函数‘main’中:
test.c:7:2: 警告: 传递‘printf’的第 1 个参数时将整数赋给指针,未作类型转换 [默认启用]
/usr/include/stdio.h:363:12: 附注: 需要类型‘const char * __restrict__’,但实参的类型为‘int’
test.c:7:2: 警告: 格式字符串不是一个字面字符串而且没有待格式化的实参 [-Wformat-security]
还有需要注意的,在用双引号括起来的字符串中,注释符 /* 属于字符串的一部分,而在注释中出现的双引号 "" 又属于注释的一部分。
#include <stdio.h>
#include <string.h>
int main (void)
{
char *str = "123/*ddddd*/456";
printf ("%s\n", str);
/*"123456"*/
return 0;
}
输出结果:
123/*ddddd*/456
再再有注意宏定义,用于定义字符串,尤其是路径
A),#define ENG_PATH_1 E:\English\listen_to_this\listen_to_this_3
B),#define ENG_PATH_2 “ E:\English\listen_to_this\listen_to_this_3”
A 为 定义路径, B 为定义字符串
4、字符串输入/输出
二、字符串函数
经过了一个月的Hi3516A的项目,现在终于有时间,静下心来继续总结C语言了。虽说过了一个月,总结到什么地方了、还有什么没总结的地方,基本上忘得差不多了,慢慢拾起来吧!
字符串函数,其原型在/kernel/include/linux/string.h有声明
这类字符串库函数的功能实现函数的答案在内核源码/kernel/lib/string.c
扩展:size_t类型为unsigned int类型,占位符使用%u或%lu,C99用%zd。
下面来介绍一些最有用和最常用的函数:
1、strlen() 函数
#include <string.h>
size_t strlen(const char *s);
函数功能:用来统计字符串中有效字符的个数
功能实现函数:
size_t strlen (const char *s)
{
const char *sc;
for (sc = s; *sc != '\0'; ++sc)
/"nothing"/
return sc - s;
}
strlen()函数被用作改变字符串长度,例如:
#include <stdio.h>
#include <string.h>
void fit (char *, unsigned int);
int main (void)
{
char str[] = "hello world";
fit (str, 7);
puts (str);
puts (str + 8);
return 0;
}
void fit (char *string, unsigned int size)
{
if (strlen (string) > size)
*(string + size) = '\0';
}
输出结果:
hello w
rld
可以看出:fit()函数在数组的第8个元素中放置了一个'\0'字符来代替原有的o字符。puts()函数输出停在o字符处,忽略了数组的其他元素。然而,数组的其他元素仍然存在。
puts (str + 8);
表达式str + 8是str[8]即'r'字符的地址。因此puts()显示这个字符并且继续输出知道遇到原字符串中的空字符。
2、strcat()函数
#include <string.h>
char *strcat(char *dest, const char *src);
函数功能:合并两个字符串
缺点:超出字符串存储区范围的话,有可能修改数组以外的存储区这会导致严重错误
功能实现函数:
char *strcat(char *dest, const char *src)
{
char *tmp = dest;
while (*dest)
dest++;
while ((*dest++ = *src++) != '\0')
;
return tmp;
}
strcat()函数它将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第二个字符串并没有改变,例如:
#include <stdio.h>
#include <string.h>
#define SIZE 80
int main (void)
{
char flower[SIZE];
char addon[]= " is beautiful";
gets (flower);
strcat (flower, addon);
puts (flower);
return 0;
}
输出结果:
rose
rose is beautiful
3、strncat()函数
上面有提到strcat()函数的缺点,它并不能检查第一个数组是否能够容纳第二个字符串。如果没有为第一个数组分配足够大的空间,多出来的字符溢出到相邻存储单元时就会出现问题。
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
函数功能:合并两个字符串
功能实现函数:
char *strncat(char *dest, const char *src, size_t count)
{
char *tmp = dest;
if (count) {
while (*dest)
dest++;
while ((*dest++ = *src++) != 0) {
if (--count == 0) {
*dest = '\0';
break;
}
}
}
return tmp;
}
例如:
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
int main (void)
{
char flower[SIZE];
char addon[]= " is beautiful";
char bug[BUGSIZE];
int arg;
puts ("what is your favorite flower");
gets (flower);
if ((strlen (addon) + strlen (flower) + 1) <= SIZE)
strcat (flower, addon);
puts (flower);
puts ("what is your favorite flower");
gets (bug);
arg = BUGSIZE - strlen (bug) -1;
strncat (bug, addon, arg);
puts (bug);
return 0;
}
输出结果:
what is your favorite flower
Rose
Rose is beautiful
what is your favorite flower
MuDan
MuDan is bea
4、strcmp()函数
#include <string.h>
int strcmp(const char *s1, const char *s2);
函数功能:比较两个字符串的大小,比较依据的是ASCII码
返回值:依次比较字符串每一个字符的ASCII码,如果两个字符串参数相同,返回0;如果第一个字符串参数较大,则返回1,如果第二个字符串较大,则返回-1。
功能实现函数:
int strcmp(const char *cs, const char *ct)
{
unsigned char c1, c2;
while (1) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
}
return 0;
}
需要注意的是:
strcmp()函数比较的是字符串,而不是数组和字符。它可以用来比较存放在不同大小数组里的字符串。
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define STOP "q"
int main (void)
{
char str[SIZE];
while (gets (str) != NULL && strcmp (str, STOP))
{
printf ("hello\n");
sleep (1);
}
return 0;
}
这里提一下:strcmp比较区分字母大小写 相当是比较的时候纯粹按照ascii码值来比较从头到尾。
而 stricmp 是不区分字母的大小写的。
5、strncmp()函数
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
函数功能:只比较两个字符串里前n个字符
功能实现函数:
int strncmp(const char *cs, const char *ct, size_t count)
{
unsigned char c1, c2;
while (count) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
count--;
}
return 0;
}
6、strcpy()函数
#include <string.h>
char *strcpy(char *dest, const char *src);
函数功能:把一个字符串复制到另外一个字符数组里
缺点:有可能修改不属于数组的存储区,这会导致错误
功能实现函数:
char *strcpy(char *dest, const char *src)
{
char *tmp = dest;
while ((*dest++ = *src++) != '\0')
/* nothing */;
return tmp;
}
例如:
char str1[30];
char str2[ ] = "hello";
char *pts1 = str1;
char *pts2 = str2;
pts1 = pts2 (错误)
strcpy (pts1, pts2); (正确)
因为pts1和pts2都是指向字符串的指针,上面的表达式只复制字符串的地址而不是字符串本身。
字符串之间的复制应使用strcpy ()函数,它在字符串运算中的作用等价于赋值运算符。
总之,strcpy()接受两个字符串指针参数。指向最初字符串的第二个指针可以是一个已声明的指针、数组名或字符串常量。指向复制字符串的第一个指针应指向空间大到足够容纳该字符串的数据对象,比如一个数组。记住,声明一个数组将为数据分配存储空间,而声明一个指针只为一个地方分配存储空间。
#include <stdio.h>
#include <string.h>
#define WORDS "best"
#define SIZE 40
int main (void)
{
char *orig = WORDS;
char copy[SIZE] = "Be the best that you can be.";
char *ps;
puts (orig);
puts (copy);
ps = strcpy (copy + 7 , orig);
puts (copy);
puts (ps);
return 0;
}
输出结果:
best
Be the best that you can be.
Be the best
best
上述例子,运用了strcpy()函数的两个属性。首先,它是char*类型,它返回的是第一个参数的值,即一个字符的地址;其次,第一个参数不需要指向数组的开始,这样就可以只复制数组的一部分。
7、strncpy()函数
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
函数功能:把一个字符串复制到另外一个字符数组里
功能实现函数:
har *strncpy(char *dest, const char *src, size_t count)
{
char *tmp = dest;
while (count) {
if ((*tmp = *src) != 0)
src++;
tmp++;
count--;
}
return dest;
}
上面有讲,strcpy()函数的缺点,就是不能检查目标字符串是否容纳得下源字符串。使用strncpy()函数,它的第三个参数可指明最大可复制的字符数。
#include <stdio.h>
#include <string.h>
int main()
{
char src[40];
char dest[12];
memset(dest, '\0', sizeof(dest));
strcpy(src, "This is w3cschool.cc");
strncpy(dest, src, 10);
printf("最终的目标字符串: %s\n", dest);
return(0);
}
输出结果:
最终的目标字符串: This is w3
8、strstr()函数
#include <string.h>
char *strstr(const char *haystack, const char *needle);
函数功能:在一个字符串中查找另外一个字符串所在位置
功能实现函数:
char *strstr(const char *s1, const char *s2)
{
size_t l1, l2;
l2 = strlen(s2);
if (!l2)
return (char *)s1;
l1 = strlen(s1);
while (l1 >= l2) {
l1--;
if (!memcmp(s1, s2, l2))
return (char *)s1;
s1++;
}
return NULL;
}
例如:
#include <stdio.h>
#include <string.h>
int main()
{
const char haystack[20] = "W3CSchool";
const char needle[10] = "3";
char *ret;
ret = strstr(haystack, needle);
printf("子字符串是: %s\n", ret);
return(0);
}
输出结果:
子字符串是: 3CSchool
如果needle字符串不是haystack的一部分,则会出现警告:
assignment discards ‘const’ qualifier from pointer target type
9、memset()函数
#include <string.h>
void *memset(void *s, int c, size_t n);
函数功能:可以把字符数组中所有的字符存储区填充同一个字符数据
功能实现函数:
void *memset(void *s, int c, size_t count)
{
char *xs = s;
while (count--)
*xs++ = c;
return s;
}
举例:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
输出结果:
This is string.h library function
$$$$$$$ string.h library function
10、sprintf()函数
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
函数功能:按照格式把数据打印在字符数组中,形成一个字符串
#include <stdio.h>
#define SIZE 30
int main (void)
{
char str[SIZE];
sprintf (str, "%s %s %d\n", "I","love",512 );
puts (str);
return 0;
}
输出结果:
I love 512
注意:使用sprintf()和使用printf()的方法一样,只是结果字符串被存放在数组fornal中,而不是被显示在屏幕上。
再有如果想将2转化为字符串“02”,该怎么办呢?
#include <stdio.h>
int main (void)
{
char str[2];
sprintf (str, "%02d", 2);
printf ("str = %s\n", str);
return 0;
}
输出结果:
str = 02
11、memcpy函数
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
函数功能:从存储区src 复制 n 个字符到存储区dest。
参数:
dest -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
src -- 指向要复制的数据源,类型强制转换为 void* 指针。
n -- 要被复制的字节数。
返回值:该函数返回一个指向目标存储区 dest 的指针。
功能实现函数:
void *memcpy(void *dest, const void *src, size_t count)
{
char *tmp = dest;
const char *s = src;
while (count--)
*tmp++ = *s++;
return dest;
}
//示例一
#include <stdio.h>
#include <string.h>
int main ()
{
const char src[50] = "hello world!";
char dest[50];
printf("Before memcpy dest = %s\n", dest);
memcpy(dest, src, strlen(src)+1);
printf("After memcpy dest = %s\n", dest);
return(0);
}
输出结果:
Before memcpy dest =
After memcpy dest = hello world!
//示例二
#include <stdio.h>
#include <string.h>
int main ()
{
const int src[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int dest[50];
memcpy(dest, src, 9*4);
int i;
for (i = 0; i < 9 ; i++)
{
printf ("%d ", dest[i]);
}
printf ("\n");
return(0);
}
输出结果:
1 2 3 4 5 6 7 8 9
strcpy 和 memcpy 的区别:
(1)strcpy 和 memcpy 都是标准 C 库函数
(2)strcpy 提供了字符串的复制。即 strcpy 只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。
(3)strcpy函数的原型是:char* strcpy(char* dest, const char* src);
(4)memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。
(5)memcpy函数的原型是:void *memcpy( void *dest, const void *src, size_t count );
strcpy 和 memcpy 主要有以下3方面的区别:
(1)复制的内容不同。strcpy 只能复制字符串,而memcpy可以复制任何内容,例如字符串数组、整型、结构体、类等。
(2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符串结束符'\0'才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
(3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
12、memcmp函数
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
函数功能:
把存储区 str1 和存储区 str2 的前 n 个字节进行比较。
参数:
str1 -- 指向内存块的指针。
str2 -- 指向内存块的指针。
n -- 要被比较的字节数。
返回值:
如果返回值 < 0,则表示 str1 小于 str2。
如果返回值 > 0,则表示 str2 小于 str1。
如果返回值 = 0,则表示 str1 等于 str2。
示例:
#include <stdio.h>
#include <string.h>
int main (void)
{
int ret = 0;
char str1[5] = {'1','2','3','4','5'};
char str2[5] = {'1','A','B','4','5'};
ret = memcmp (str1, str2, 5);
if(ret > 0)
{
printf("str2 灏忎簬 str1\n");
}
else if(ret < 0)
{
printf("str1 灏忎簬 str2\n");
}
else
{
printf("str1 绛変簬 str2\n");
}
return 0;
}
输出结果:
str1 小于 str2
三、将字符串转换成数字
atoi()函数、atol()函数、atof()函数
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
double atof(const char *nptr);
函数功能: 分别把数字的字符串表示转换为 int、long和double形式。如果没有执行有效的转换返回0.
atoi()功能实现函数:
int my_atoi(const char *str)
{
int value = 0;
int flag = 1; //判断符号
while (*str == ' ') //跳过字符串前面的空格
{
str++;
}
if (*str == '-') //第一个字符若是‘-’,说明可能是负数
{
flag = 0;
str++;
}
else if (*str == '+') //第一个字符若是‘+’,说明可能是正数
{
flag = 1;
str++;
}//第一个字符若不是‘+’‘-’也不是数字字符,直接返回0
else if (*str >= '9' || *str <= '0')
{
return 0;
}
//当遇到非数字字符或遇到‘\0’时,结束转化
while (*str != '\0' && *str <= '9' && *str >= '0')
{
value = value * 10 + *str - '0'; //将数字字符转为对应的整形数
str++;
}
if (flag == 0) //负数的情况
{
value = -value;
}
return value;
}
测试:
#include <stdio.h>
int my_atoi(const char *str)
{
int value = 0;
int flag = 1; //判断符号
while (*str == ' ') //跳过字符串前面的空格
{
str++;
}
if (*str == '-') //第一个字符若是‘-’,说明可能是负数
{
flag = 0;
str++;
}
else if (*str == '+') //第一个字符若是‘+’,说明可能是正数
{
flag = 1;
str++;
}//第一个字符若不是‘+’‘-’也不是数字字符,直接返回0
else if (*str >= '9' || *str <= '0')
{
return 0;
}
//当遇到非数字字符或遇到‘\0’时,结束转化
while ((*str != '\0')&& (*str <= '9') && (*str >= '0'))
{
value = value * 10 + *str - '0'; //将数字字符转为对应的整形数
str++;
}
if (flag == 0) //负数的情况
{
value = -value;
}
return value;
}
int main(void)
{
int i = my_atoi(" -1234dd");
printf("%d\n", i);
return 0;
}
输出结果:
-1234
测试:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int val1, val2;
long val3, val4;
double val5, val6;
val1 = atoi ("512you");
val2 = atoi ("you512");
val3 = atol ("1000000you");
val4 = atol ("you1000000");
val5 = atof ("3.14you");
val6 = atof ("you3.14");
printf ("val1 = %d\n", val1);
printf ("val2 = %d\n", val2);
printf ("val3 = %lu\n", val3);
printf ("val4 = %lu\n", val4);
printf ("val5 = %lg\n", val5);
printf ("val6 = %lg\n", val6);
return 0;
}
输出结果:
val1 = 512
val2 = 0
val3 = 1000000
val4 = 0
val5 = 3.14
val6 = 0
itoa函数功能实现:
void itoa(int num,char str[] )
{
int sign = num,i = 0,j = 0;
char temp[11];
if(sign<0)//判断是否是一个负数
{
num = -num;
};
do
{
temp[i] = num%10+'0';
num/=10;
i++;
}while(num>0);
if(sign<0)
{
temp[i++] = '-';//对于负数,要加以负号
}
temp[i] = '\0';
i--;
while(i>=0)//反向操作
{
str[j] = temp[i];
j++;
i--;
}
str[j] = '\0';
}
测试:
#include <stdio.h>
void my_itoa(int num,char str[] )
{
int sign = num,i = 0,j = 0;
char temp[11];
if(sign<0)//判断是否是一个负数
{
num = -num;
};
do
{
temp[i] = num%10+'0';
num/=10;
i++;
}while(num>0);
if(sign<0)
{
temp[i++] = '-';//对于负数,要加以负号
}
temp[i] = '\0';
i--;
while(i>=0)//反向操作
{
str[j] = temp[i];
j++;
i--;
}
str[j] = '\0';
}
int main (void)
{
char arr[10]= {0};
my_itoa (-123,arr);
printf ("%s\n", arr);
}
输出结果:
-123
四、字符转十六进制
void StrToHex(char *pbDest, char *pbSrc, int nLen)
{
char h1,h2;
char s1,s2;
int i;
for (i=0; i<nLen/2; i++)
{
h1 = pbSrc[2*i];
h2 = pbSrc[2*i+1];
s1 = toupper(h1) - 0x30;
if (s1 > 9)
s1 -= 7;
s2 = toupper(h2) - 0x30;
if (s2 > 9)
s2 -= 7;
pbDest[i] = s1*16 + s2;
}
}