一、C语言中的内存分布区
1)BSS段(bss segment):常是指用来存放程序中未初始化的全局变量的一块内存区域。
2)数据段(data segment):用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
3)代码段(code segment/text segment): 用来存放程序执行代码的一块内存区域。这部分区域的代码属于只读。在代码段中,字符串常量也属于这部分。
4)堆(heap):堆是用于存放进程运行中被malloc和free动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
5)栈(stack):栈又称堆栈,存放程序的局部变量除此以外,在函数被调用时,栈用来传递参数和返回值等。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。
内存操作函数:
1)memset()
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入
参数:s:需要操作内存s的首地址
c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
n:指定需要设置的大小
返回值:s的首地址
#include <stdio.h>
int main()
{
int a[10];
memset(a, 97, sizeof(a)); //97对应'a'
memset(a, 98, sizeof(a)-sizeof(int)*5); //98对应'b',
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%c ", a[i]);
}
}
执行结果:b b b b b a a a a a
2)memcpy()
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:dest:目的内存首地址
src:源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错
n:需要拷贝的字节数
返回值:dest的首地址
#include <stdio.h>
int main()
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int b[10];
memcpy(b, a, sizeof(a));
for (int i = 0; i < 10; i++)
{
printf("%d, ", b[i]);
}
}
执行结果:b b b b b a a a a a
可能出现的情况:
- dest的长度小于n的字节数,出现报错
- dest和src所指的内存空间存在内存重叠,导致没法得出自己想要的结果
3)memmove()
memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。
2种内存重叠方式:
这第1种内存重叠方式中,memcpy和memmove得到的结果是一样的;但在第2种内存重叠方式中,memcpy无法得到正确的结果。接下来看看他们的具体实现:
void *memcpy(void *dst,void *src,size_t n)
{
char *tmp = (char *)dst;
char *p = (char *)src;
while(n--)
{
*tmp++ = *p++; //从前往后复制
}
return dst;
}
void *my_memmove(void *dst,void *src,size_t n)
{
char *p1 = (char *)dst;
char *p2 = (char *)src;
if(p1 > p2 && p2+n > p1) //对于第二种内存覆盖进行了讨论
{
p1 = p1+n-1;
p2 = p2+n-1;
while(n--)
{
*p1-- = *p2--; //从后往前复制
}
}
else
{
while(n--)
{
*p1++ = *p2++; //从前往后复制
}
}
return dst;
}
从具体实现我们可以知道,当出现第1种内存重叠时,memcpy和memmove都是从前往后复制,因此不会影响结果。但是出现第2种内存重叠时,从前往后复制的话就会出现问题,原本src末尾的一些数据会丢失,因此memmove对memcpy进行了改进,增加了这一种情况出现时的做法:从后往前复制。这就是memmove比memcpy多的一个考虑点,所以说,在实际使用时,使用memmove是比memcpy更安全的。
【疑惑】在VS2017中使用这两个函数进行实验时发现他们竟然没有区别,希望有大佬可以告诉我是为什么...
4) memcmp()
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
参数:s1:内存首地址1
s2:内存首地址2
n:需比较的前n个字节
返回值:相等:=0
大于:>0
小于:<0
#include <stdio.h>
#include <string.h>;
int main()
{
int a[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int b[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int flag = memcmp(a, b, sizeof(a));
printf("flag = %d\n", flag);
}
执行结果:flag = 0
假设使用上述代码,只改变a和b的值,注意以下代码和返回结果:
int a[10] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int b[10] = { 0, 1, 5, 6, 7, 8, 9, 1, 2, 3 };
//执行结果:flag=1
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int b[10] = { 1, 2, 5, 0, 0, 0, 0, 0, 0, 0 };
//执行结果:flag=-1
char a[] = "accdefg";
char b[] = "abcdefg";
//执行结果:flag=1
char a[] = "abcdefg";
char b[] = "acaaaaa";
//执行结果:flag=-1
可以看出,在两个数组中元素的逐个比较时,最先出现更大数的数组比另外一个数组大。
二、指针与字符串
#include<stdio.h>
int main()
{
char *s1="abcde";
char s2[]={"abcde"};
printf("%s,%c%s,%c\n",s1,*s1,s1+1,s1[1]); //注意当输出一个字符串时使用的是指针
printf("%s,%c,%s,%c\n",s2,*s2,s2+1,s2[1]); //输出一个字符时使用的是对指针的解引用
return 0;
}
运行结果为:
abcde,a,bcde,b
abcde,a,bcde,b
由此可见,字符数组和字符指针在使用上是相似的。但是两者又是有区别的:
字符指针
字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以 \0 作为串的结束。
#include <stdio.h>
int main(void)
{
char *a= "abcd" ;
printf("输出字符:%c,", *a); //输出字符,使用"%c"
printf("输出字符串:%s", a); //输出字符串,使用"%s"
}
运行结果为:
输出字符:a, 输出字符串:abcd
【注意】:字符指针a所指向的地址为存放在代码段中的字符串常量,该内存区域只可读不可写。
字符数组
字符数组是由若干个数组元素组成的,它可用来存放整个字符串(即用字符数组来存放字符串)。在C语言中,将字符串作为字符数组来处理(c++中不是)。
在C中:
在C++中:
初始化方式:
在C中一个字符串的结束标志为'\0',在平时常见的字符串如“I am a string”中末尾会默认添加一个'\0'。
char str[]={"hello"}; //正确赋值,系统自动加入\0
char str[]="hello"; //正确赋值,系统自动加入\0
char str[]={'h','e','l','l','o','\0'} //正确赋值,但使用该方式时记得要给字符串留足够的位置存放'\0'
//下面是错误的使用方式(整体赋值只能出现在字符数组初始化时使用)
char str[20];
str="hello";
总结:
char str1[] = {'h', 'i', '\0'}; //变量,可读可写
char str2[] = "hi"; //变量,可读可写
char *str3 = "hi"; //变量,可读不可写,str3指向字符串常量“hi”中首字符的地址
char *str4 = {'h', 'i', '\0'}; //错误赋值方式
因此,C语言中使用字符指针和字符数组都是可以用来表示和存放字符串的。
三、字符串处理函数
1)strcpy()
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
参数:dest:目的字符串首地址
src:源字符首地址
返回值:成功:返回dest字符串的首地址
失败:NULL
如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);
//执行结果
hello world
2)strncpy()
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。
参数:dest:目的字符串首地址
src:源字符首地址
n:指定需要拷贝字符串个数
返回值:成功:返回dest字符串的首地址
失败:NULL
char dest[20] ;
char src[] = "hello world";
strncpy(dest, src, 5);//拷贝了五个字符过去,不包含字符串末尾'\0'
printf("%s\n", dest);//输出该字符串时因为没有末尾'\0',后面会有乱码出现
dest[5] = '\0';
printf("%s\n", dest);
//执行结果
hello烫烫烫烫烫烫烫烫烫汰觠玷?
hello
3)strcat()
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:dest:目的字符串首地址
src:源字符首地址
返回值:成功:返回dest字符串的首地址
失败:NULL
char str[20] = "123"; //目的字符串空间一定要足够
char *src = "hello world";//不然在用该函数时会出现错误
printf("%s\n", strcat(str, src));
//执行结果
123hello world
4)strncat()
char *strncat(char *dest, const char *src, size_t n);
功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数:dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串个数
返回值:成功:返回dest字符串的首地址
失败:NULL
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));//会自动把'\0'添加过去,因此不会乱码
//执行结果
123hello
5)strcmp()
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:s1:字符串1首地址
s2:字符串2首地址
返回值:相等:0
大于:>0 在不同操作系统strcmp结果会不同 返回ASCII差值
小于:<0
char *str1 = "hello world";
char *str2 = "hello wozzlzdppp"; //在每个字符逐个比较的过程中,最先出现更大字符的字符串大
if (strcmp(str1, str2) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
//执行结果
str1>str2
6)strncmp()
int strncmp(const char *s1, const char *s2, size_t n);
功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小
参数:s1:字符串1首地址
s2:字符串2首地址
n:指定比较字符串的数量
返回值:相等:0
大于: > 0
char *str1 = "hello world";
char *str2 = "hello wozzlzdppp";
if (strncmp(str1, str2, 5) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
//执行结果
str1=str2
7)sprintf()
int sprintf(char *str, const char *format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0' 为止。
参数:str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:成功:实际格式化的字符个数
失败: - 1
char dst[100] = { 0 };
int a = 10;
char src[] = "hello world";
printf("a = %d, src = %s", a, src);
printf("\n");
int len = sprintf(dst, "a = %d, src = %s", a, src);
printf("dst = \" %s\"\n", dst);
printf("len = %d\n", len);
//执行结果
a = 10, src = hello world
dst = " a = 10, src = hello world"
len = 25
8)sscanf()
int sscanf(const char *str, const char *format, ...);
功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:成功:参数数目,成功转换的值的个数
失败: - 1
char src[] = "a=10, b=20";
int a;
int b;
sscanf(src, "a=%d, b=%d", &a, &b);
printf("a:%d, b:%d\n", a, b);
//执行结果
a:10, b:20
9)strchr()
char *strchr(const char *s, int c);
功能:在字符串s中查找字母c出现的位置,查找一个字符
参数:s:字符串首地址
c:匹配字母(字符)
返回值:成功:返回第一次出现的c地址
失败:NULL
char src[] = "ddda123abcd";
char *p = strchr(src, 'a');
printf("p = %s\n", p);
//执行结果
a123abcd
10)strstr()
char *strstr(const char *haystack, const char *needle);
功能:在字符串haystack中查找字符串needle出现的位置,查找一个字符串
参数:haystack:源字符串首地址
needle:匹配字符串首地址
返回值:成功:返回第一次出现的needle地址
失败:NULL
char src[] = "ddddabcd123abcd333abcd";
char *p = strstr(src, "abcd");
printf("p = %s\n", p);
//执行结果
p = abcd123abcd333abcd
11)strtok()
char *strtok(char *str, const char *delim);
功能:来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。
参数:str:指向欲分割的字符串
delim:为分割字符串中包含的所有字符
返回值:成功:分割后字符串首地址
失败:NULL
- 在第一次调用时:strtok()必需给予参数s字符串
- 往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针
char a[100] = "adc*fvcv.ebcy*hghbdfg$casdert";
char *s = strtok(a, ".*$");//将'.','*','$'分割的子串取出
while (s != NULL)
{
printf("%s\n", s);
s = strtok(NULL, ".*$");//将'.','*','$'分割的子串取出
}
//执行结果
adc
fvcv
ebcy
hghbdfg
casdert
12)atoi()
#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符('\0')才结束转换,并将结果返回返回值。
参数:nptr:待转换的字符串
返回值:成功转换后整数
- atof():把一个小数形式的字符串转化为一个浮点数。
- atol():将一个字符串转化为long类型
char str1[] = " -10";
int num1 = atoi(str1);
printf("num1 = %d\n", num1);
char str2[] = "0.123";
float num2 = atof(str2);
printf("num2 = %f\n", num2);
char str3[] = "123L";
long num3 = atol(str3);
printf("num3 = %ld\n", num3);
//执行结果
num1 = -10
num2 = 0.123000
num3 = 123