这篇文章有一些基本的前驱内容,可以先看看大概了解https://blog.csdn.net/qq_43265890/article/details/89568638
或者你可以直接看我总结的:
另外关于堆区,栈区,常量区的讨论网上也有很多,这里再讨论一些细枝末节的事情
char *s = “abc”;这个在常量区,常量区的值可读不可写
,如:s[1] = “d”;这样的方式会报错
,但是可以给指针重新赋值 s = “add”。
char s[] = “abc”; s[]是一个数组,在栈中占3个字节,存放的分别是’a’,‘b’,‘c’; s不是指针。
sizeof(s)得到的结果也是数组长度,但为4(包括’\0’),可以认为,s成为了一种新的数据类型
,此概念在帮助理解多维数组。
以上问题的根本原因
在于`:
char * 是数据类型,即指针。
指针的特殊性包含两层意思,一层他是指针,有指针固定的大小,存放的东西是地址。另一层,他不是什么地址都存放,他要存放char类型的地址。
字符串常量在c中类型是char* ,也就是说,他本来就是一个指针,而且是char的指针。
(很奇怪吧,你可以理解成,char* s = “abcd” 是在常量区保存了"abcd",显示赋值时,其实是把这个地址给了s。别问为啥这么反常,就这么定义的)
左边是char* 右边也是char*,能够赋值是理所当然的事情!所谓类型系统,目的就是要保证赋值的类型是兼容的。
char a;
char * pa = &a;
为何a不能直接赋值给pa?因为pa是char*,而a是char,类型不兼容。
为什么&a能够赋值给pa?因为&a的结果类型是char*。
并非要地址才能赋值给指针,任何东西都能赋值给指针,只要类型一致
!
如:
char* pa = (char* )1;
强制类型转换,让常数1转型为char*,就存进了指针里面。
地址是什么?任何在内存的变量,都有类型,和首地址,这两个能够唯一确定一个变量。类型指示了所占空间的大小,首地址指示了开始的位置,有这两个东西就能操作变量。
指针就是对应这两个东西的变量,指针的类型说的是目标地址上变量的类型,而不是指针本身有类型,指针就一种,他的大小取决于cpu,32位寻址的cpu就是一个32位的指针。
因此,可以用一个32位的无符号整数赋值给指针,让他指向任何地址。
Tips
char p[] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’,‘g’,‘h’}; sizeof§的结果是8;
char p[] = {“abcdrfgh”} sizeof§的结果是9;
strcpy的实现,这里考虑内存重叠
的问题(请考虑一个问题,如果destination是char[],source是char *,是不是就不会发生内存重叠了呢?我的看法是支持的,所以这里考虑的内存重叠只会在destination是char[],source也是char[]的情况下才会发生)
首先,
不考虑内存重叠
char * strcpy(char *dst,const char *src) //[1]
{
assert(dst != NULL && src != NULL); //[2]
char *ret = dst; //[3]
while ((*dst++=*src++)!='\0'); //[4]
return ret;
}
重叠问题的分析:
(1)src + count <= dst或dst + count <= src即不重叠,这时正常从低字节依次赋值
(2)dst + count>src 这时部分重叠,但不影响赋值拷贝,也可以从低字节开始
(3)src + count>dst 这时也是部分重叠,但影响拷贝,应该从高字节逆序开始。
其实第1种的第二种情况和第二种同属于一类,dst<src即可。
所以其实可以分为2类
(1)src + count <= dst || dst<src
(2)src + count>dst
char *strcpy_imp(char dst[], const char *src, int length)
{
assert(dst != NULL&&src != NULL);
if (dst<src || src + length <= dst)
{
char *s = dst;
while (length--)
{
*dst++ = *src++;
}
return s;
}
else
{
dst = dst + length - 1;
src = src + length - 1;
while (length--)
{
*dst-- = *src--;
}
return ++dst; //不要忘了++;
}
}
总结:内存重叠是考点,另外要注意的:
[1]const修饰
源字符串参数用const修饰,防止修改源字符串。
[2]空指针检查
(A)不检查指针的有效性,说明答题者不注重代码的健壮性。
(B)检查指针的有效性时使用assert(!dst && !src);
char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。
©检查指针的有效性时使用assert(dst != 0 && src != 0);
直接使用常量(如本例中的0)会减少程序的可维护性。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[3]返回目标地址
(A)忘记保存原始的strdst值。
[4]’\0’
(A)循环写成while (*dst++=*src++);明显是错误的。
(B)循环写成while (*src!=’\0’) *dst++=*src++;
循环体结束后,dst字符串的末尾没有正确地加上’\0’。
2.为什么要返回char *?
返回dst的原始值使函数能够支持链式表达式。
链式表达式的形式如:
int l=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是错误的。
其一,源字符串肯定是已知的,返回它没有意义。
其二,不能支持形如第二例的表达式。
其三,把const char *作为char *返回,类型不符,编译报错。
关于memcpy和strcpy的区别
strcpy和memcpy都是标准C库函数,它们有下面的特点。
strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。
已知strcpy函数的原型是:char* strcpy(char* dest, const char* src);
memcpy提供了一般内存的复制。即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
2.memcpy与memmove
功能:从存储区 src 复制 n 个字符到存储区 dest。
如果写出的代码如下:你也没考虑内存重叠!
void *memcpy(void *dest, const void *src, size_t n) {
if (!dest || !src)
return NULL;
char *d = (char *) dest;
const char *s = (const char *) src;
size_t i = 0;
for (i = 0; i < n; i++) {
*d++ = *s++;
}
return dest;
}
优化也是消除内存重叠!
我们看一下man手册中的memcpy函数:
The memcpy() function copies n bytes from memory area src to memory area dest.
The memory areas must not overlap.
Use memmove(3) if the memory areas do overlap。
翻译一下就是,memcpy函数从存储区 src 复制 n 个字符到存储区 dest。存储区不能重叠!
如果重叠了,需要使用memmove。
实现如下:
void *memcpy1(void *dest, const void *src, size_t n) {
if (!dest || !src)
return NULL;
char *d = (char *) dest;
const char *s = (const char *) src;
if (d > s && d < s + n) {
d = d + n - 1;
s = s + n - 1;
while (n--)
*d-- = *s--;
} else {
while (n--)
*d++ = *s++;
}
return dest;
}
上述也是memmove函数实现!
都说到这了,咱把strcat和strlen都说了吧哈哈
//函数strcat的原型是char* strcat(char* des, char* src),des 和 src 所指内存区域不可以重叠且 des 必须有足够的空间来容纳 src 的字符串。
char* strcat(char* des, const char* src) // const表明为输入参数
{
assert((des != NULL) && (src != NULL));
char* address = des;
while (*des != '\0') // 移动到字符串末尾
++des;
while (*des++ = *src++)
;
return address;
}
函数strlen的原型是size_t strlen(const char *s),其中 size_t 就是 unsigned int。
strlen 与 sizeof 的区别:详细见另一篇文章
sizeof是运算符,strlen是库函数。
sizeof可以用类型、变量做参数,而strlen只能用 char* 变量做参数,且必须以\0结尾。
sizeof是在编译的时候计算类型或变量所占内存的大小,而strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度。
数组做sizeof的参数不退化,传递给strlen就退化为指针了。
int strlen(const char* str)
{
assert(str != NULL);
int len = 0;
while ((*str++) != '\0')
++len;
return len;
}