char *,char[],strcpy的终极解答

这篇文章有一些基本的前驱内容,可以先看看大概了解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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值