char *那些事儿

本文探讨了C++中与`char *`相关的常见问题,包括`char *`的赋值、作为函数参数和返回值的注意事项。通过示例说明了直接使用`str.toStdString().data()`可能导致的空指针问题,并提出了解决方案。强调了在处理`char *`时应避免浅拷贝,以及在函数参数和返回值中正确使用`const char *`的重要性。

引子

有这样一段代码:

int main()
{
	QString str = "hello";
	auto charStr = str.toStdString().data();
	std::cout << charStr << " world" << std::endl;
}

请问输出结果是什么?

答案是: world

这是为什么呢?首先让我们来看下Qt的toStdString()函数的实现。其主要包含以下部分:

inline std::string QString::toStdString() const
{ return toUtf8().toStdString(); }

Q_REQUIRED_RESULT QByteArray toUtf8() const &
    { return toUtf8_helper(*this); }

inline std::string QByteArray::toStdString() const
{ return std::string(constData(), length()); }

inline const char *QByteArray::constData() const
{ return d->data(); } // d:QTypedArrayData<char> *d;

template <class T>
struct QTypedArrayData
    : QArrayData
{
const T *data() const { return static_cast<const T *>(QArrayData::data()); }
...
};

而std::string的构造函数和data()函数的实现如下:

basic_string(const _Elem *_Ptr, size_type _Count)
		: _Mybase()
{	// construct from [_Ptr, _Ptr + _Count)
	_Tidy();
	assign(_Ptr, _Count);
}

_Myt& assign(const _Elem *_Ptr, size_type _Count)
{	// assign [_Ptr, _Ptr + _Count)
	_DEBUG_POINTER_IF(_Count != 0, _Ptr);
	if (_Inside(_Ptr))
		return (assign(*this,_Ptr - this->_Myptr(), _Count));	// substring

	if (_Grow(_Count))
	{	// make room and assign new stuff
		_Traits::copy(this->_Myptr(), _Ptr, _Count);
		_Eos(_Count);
	}
	return (*this);
}

const _Elem *data() const _NOEXCEPT
{	// return pointer to nonmutable array
	return (this->_Myptr());
}

const value_type *_Myptr() const
{	// determine current pointer to buffer for nonmutable string
	return (_Get_data()._Myptr());
}

const value_type *_Myptr() const
{	// determine current pointer to buffer for nonmutable string
	return (this->_BUF_SIZE <= _Myres
		? _Unfancy(_Bx._Ptr)
		: _Bx._Buf);
}

template<class _Ty> inline
	_Ty * _Unfancy(_Ty * _Ptr)
{	// do nothing for plain pointers
	return (_Ptr);
}

~basic_string() _NOEXCEPT
{	// destroy the string
	_Tidy(true);
}
void _Tidy(bool _Built = false,size_type _Newsize = 0)
{	// initialize buffer, deallocating any storage
	if (!_Built)
		;
	else if (this->_BUF_SIZE <= this->_Myres())
	{	// copy any leftovers to small buffer and deallocate
		pointer _Ptr = this->_Bx()._Ptr;
		this->_Getal().destroy(_STD addressof(this->_Bx()._Ptr));
		if (0 < _Newsize)
			_Traits::copy(this->_Bx()._Buf,
				_Unfancy(_Ptr), _Newsize);
		this->_Getal().deallocate(_Ptr, this->_Myres() + 1);
	}
	this->_Myres() = this->_BUF_SIZE - 1;
	_Eos(_Newsize);
}
		
void _Eos(size_type _Newsize)
{	// set new length and null terminator
	auto& _Dx = this->_Get_data();
	_Traits::assign(_Dx._Myptr()[_Dx._Mysize = _Newsize], _Elem());
}

从toStdString()的实现看,str.toStdString()其实就是由**return std::string(constData(), length());**返回一个临时变量,假设这个变量为nstr。然后执行nstr.data()。而std::string的data()函数的实质就是将自己存放字符串数据的指针返回给调用者。所以,charStr拿到的其实就是nstr中存放字符串数据的指针的地址。

然而,由于nstr只是个临时变量,所以当auto charStr = str.toStdString().data();这条语句执行完以后,nstr就被释放了。从std::string的析构函数的实现中可以看到,在释放时,std::string对象中存放字符串数据的指针被重新赋值了。新的值为char()。所以,charStr的最终结果是一个空字符串。

那如何才能正确使用str.toStdString().data()的值呢?有两种办法,一种是:

auto stdStr = str.toStdString();
auto charStr = stdStr.data();

另外一种是:

char charStr[100];
strcpy_s(charStr, str.size() + 1, str.toStdString().data());
std::cout << "char str = " << charStr << std::endl;

从第二种方法我们可以看出,上面这个问题其本质其实就是char *类型的变量的赋值问题。因为std::string的data()函数返回的就是一个char *,如果直接使用=赋值,则是一种浅拷贝,直接使用的是char *的地址,而这里由于返回的char *的地址是一个临时变量中的地址,所以不能使用浅拷贝,需要通过深拷贝将数据复制到一个新的地址中。

char *的赋值

char str1[10] = "hello world"; // OK
char *str2 = "hello world"; // C++11中不可以

char *str2 = str1; // 浅拷贝,str1变化,str2也会变化
char *str3 = new char[20];
strcpy_s(str3, strlen(str1) + 1, str1); // 深拷贝,str1变化,str3不会变化

注:strlen()计算的长度是不包含"\0"的,所以在使用strcpy_s时,要考虑加上"\0"后的长度。

const char*作为函数参数

char *作为参数时需要注意以下几点:

  • 这个参数最好不要作为返回值。比如有下面这样的函数:
    const char * func(const char *arg)
    {
    	return arg;
    }
    
    现在调用这个函数:
    QString str = "hello";
    auto data = func(str.toStdString().data());
    std::cout << data << std::endl;
    
    最终结果只能输出空白。其原因就是上面所提到的。日常开发过程中,碰到这样的问题要去查找原因都很困难,所以在定义函数时,最好不要将参数中的char*直接返回。
  • 尽量通过深拷贝来使用char *参数。使用深拷贝可以避免将这个参数的地址赋值给其他作为返回值的变量。

const char *作为函数返回值

  • 不要将参数直接返回;
  • 设置返回值时,要注意检查这个值是否是一个临时变量的地址;
  • 对于C++而言,在需要返回一个字符串时,推荐使用std::string。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值