在C语言中可以做但在C++中不能做的事情有哪些,你在编写C++代码时最错过哪些特性?
我能想到的几个:
- 在C中,我们可以将任何类型的指针分配给void指针而无需使用强制转换,但在C++中不行。
- 声明变量名为C++关键字,而这在C中则可以。
编辑:感谢 @sbi 指出:
1. 应该是:在C中,我们可以将void指针分配给任何类型的指针,但在C++中不行。
在C语言中可以做但在C++中不能做的事情有哪些,你在编写C++代码时最错过哪些特性?
我能想到的几个:
编辑:感谢 @sbi 指出:
1. 应该是:在C中,我们可以将void指针分配给任何类型的指针,但在C++中不行。
注意:我猜我会因为这个问题而受到责备,但是,毕竟这是一个针对C++开发人员的C++问题,所以...
在C中可以做什么而在C ++中无法做什么?你在使用C ++编码时最想念哪些特性?
作为一名C++开发人员,我不会错过C的任何东西,无论是C99还是其他。
我并不是出于恶意写这篇文章。这是一个问题,针对那些错过了C/C99某些特性的C++开发人员,因为他们忽略了C++的基本特性。我确信这个问题及其答案忽略了C++中可行或更好的替代方案(不,"C++向量很讨厌"的评论只是一个虚假的理由)。
这就是为什么我将在这里讨论每一个所谓的“缺失功能”...
可变长度数组是C99的一种语言特性。它的主要优点是:
对于大多数常见情况,std::vector
可以胜任工作,并且具有更多的功能。例如,除非我错了,可变长度数组具有以下缺点:
向量可以调整大小,并且可以返回。使用 C++0x(和右值引用),您可以使用移动语义返回向量,这意味着不需要无用的临时对象。它可以放在结构/类中,可以是 extern、static。您可以使用默认值,数组内容,容器或者使用 C++0x 使用初始化列表进行初始化。
即使在这之后,如果您真的需要像 VLA 一样的东西,在 C++ 中,平均 C++ 开发人员可以编写基于堆栈的类似向量的容器。而且不需要完整的语言委员会更新。
只是为了好玩,我恰好发布了一个 answer with a simple proof-of-concept of a C++ VLA-like class.
C ++ 的向量大多数情况下是更好的选择,具有更多功能。在真正需要 VLA 的罕见情况下,可以通过用户定义的类来模拟其功能。
void *
强制转换为 T *
?void *
转换为另一种类型指针,这不是C语言缺少而C++具有的功能:这是弱类型与强类型之间的选择。void *
不像其他语言那样有用:在我的当前的C++ 100k行项目中,我没有出现过void *
的情况。这种语法糖假设您知道结构体的确切实现并具有其成员的公共访问权限,在C++中通常应避免这种情况。
再次声明,复合字面值不是无法通过函数、方法甚至构造函数处理的,具有上述附加功能的优势。
我了解你的感受:每当我有可能在C++中使用interface
、final
或synchronized
时,我也会感到Java的颤动...
:-P
C中的问题在于您有很多函数对不同类型执行相同的语义操作,这意味着每个函数必须有不同的名称。例如,根据OpenGroup的说法,存在以下函数:
double sin(double x);
float sinf(float x);
long double sinl(long double x);
来源:http://www.opengroup.org/onlinepubs/009695399/functions/sin.html
但是它们的名称真让人头疼,所以有人有了一个想法。大致上是使用编译器内置扩展调用正确的函数,具体根据所用参数的类型。
这就是C99中<tgmath.h>
的神奇之处。
而且这个想法看起来如此awesome以至于他们甚至提出了一个建议,在下一个C标准中为所有函数提供这个功能,大概是这样:
#define sin(x) __tgmath(x,,, \
float, sinf, long double, sinl, \
/* etc. */ \
, , sin)(x)
来源: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1340.htm
现在,令人震惊的消息是:自几十年前以来,C ++ 就已经具备了这个功能:这被称为函数重载。
例如,上面的函数在 C++ 中声明如下:
double sin (double x );
float sin (float x );
long double sin (long double x );
因此,“类型通用宏”是一种破解实现,旨在(部分地)模拟更通用的 C++ 函数重载。
猜猜看:您甚至可以为自己定义的用户定义类型添加自己的重载。
正如上面所示,每次我学习 C99 功能时,结论都是:“嘿,我已经可以在 C++ 中做到这一点了!”(并且通常在句子中某处有“更好”的词语)。
说真的,作为一个 C++ 开发者,我现在错过的是能够在工作中使用 C++0x。例如,以下 C++0x 功能:
auto
constexpr
nullptr
(void*)
(或者现在C ++程序员所做的任何内容来执行强制转换)。 - JeremyPvoid*
进行转换,而不是向其进行转换。 - sbivoid*
在C++中受到限制。那是错误的。限制的是从void*
进行类型转换,而且有很好的理由。 - sbichar *
,一个调用另一个并进行强制转换)。但是我不能说我对C ++关于符号管理的整体行为感到满意。为什么允许有符号/无符号数字类型之间的转换仅发出警告,但当通过指针访问时却使其成为错误?在我看来,两者都应该是警告或错误,C在这方面更加一致。 - krissrestrict
,但我个人不会错过VLA和<tgmath.h>。 - schotC语言本身并没有这个特性,但可以说作为C语言的杀手级特性之一的是,C89的简单语法使得编写编译器变得容易。与编写C++编译器相比,这尤其如此。
void *
转换为任何具体指针类型而无需进行强制转换的能力。()
。在C中,您可以这样做。void foo(int, int);
void bar(double);
int main() {
void (*pf)();
pf = foo;
pf(1, 2); /* valid call */
pf = bar;
pf(5.0); /* valid call */
}
在C++中是不可能实现这一点的。当然,也可以说通用的非原型函数声明是C的特性,而不是C++的特性(同样适用于C99)。
(3) 使用字符串字面值进行数组初始化时存在一些差异:在C中,尾随的\0
允许被省略,但在C++中不允许。
char str[2] = "ab"; /* valid C, not valid C++ */
(4) 在C语言中,有一些初步定义,尽管它们大多数情况下没有实际意义。
(5) 另一个大多数情况下不重要的“特性”:在C语言中,您可以使用返回值函数,但是忘记实际返回任何内容。
int foo() {
}
这段代码在C和C++中都是合法的,但在C++中,这样的函数会无条件地产生未定义行为。在C中,只有当您尝试使用返回值时,该函数才会产生未定义的行为。
foo(); /* fine in C, undefined behavior in C++ */
(6) 如果我想起来,我会随后添加一些其他的内容。
void *
转换),所以如果我在C++中工作,我可能不会错过它们中的任何一个。 - AnT stands with Russiavoid *
转换被广泛使用。 - Matt Joinervoid* void_ptr;
int* int_ptr;
int_ptr = void_ptr; // Invalid in C++, but not in C
void_ptr = int_ptr; // Valid in C and C++
void_ptr = (void*)int_ptr; // Valid in C and C++
int_ptr = (int*)void_ptr; // Valid in C and C++
new[]
更加有用。std::vector<>
。 - sbivector
在堆上分配内存,而可变长度数组(VLA)可能在栈上分配内存。由于大多数情况下堆上的内存分配比栈上的更昂贵,因此我相信这会很有用。 - Naveenstd::array
就适合你。此外,我不知道 std::vector
如何在处理非 POD(平凡标量类型) 和 POD 的方面有所不同(除了一些实现可能基于这种区别进行优化)。作为一个教授 C++ 多年的人,我可以证明初学者似乎比 C 数组更容易理解 std::vector
。最后,我还没有看到测量结果表明 std::vector
比 C 数组慢。 - sbistrlen
是罪魁祸首,而不是 C++ 的向量或字符串。 - paercebal在语法糖和类型滥用方面存在一些微妙的差异,但是它们可以轻松解决。
C语言最重要的能力是生成完全独立于外部依赖的程序。这就是为什么操作系统内核几乎普遍采用C语言编写的原因。实际上,C语言是专门为实现操作系统而设计的。虽然有可能使用C++的受限子集来编写操作系统内核,但只有在链接时才会或根本不会强制执行这些限制,因此与细微的语法差异相比,更加麻烦。
a = b;
这样的东西,并且确切地知道它在做什么。但在C++中,任何人都可以重载运算符,这意味着像这样简单的语句可能会调用一些大型的复制构造函数(或更糟糕的是,一些完全无关的东西)。当你在C++中看到 a = b;
时,你不得不去猜测(或查找)是否有人只是想捣乱。+
时编写一个-
),而他们这样做只是为了恶心你,那么你应该另找一份工作。和正常人一起用C++编码可以非常启发人:在我10多年的职业生涯中,我从未和你描述的白痴一起工作过,当一些代码效率低下时,大多数情况下,它并不在操作符重载代码中。 - paercebala = b
的例子是一个很好的例子。在 C++ 中,这意味着 a
将成为 b
的一个副本。如果 b
是一个大对象,那么你可以肯定这个复制操作会很昂贵。这里重要的部分是“如果 b
是一个大对象”。如果我们忽略“破坏者”阴谋论,这意味着在 C++ 中,类型是重要的,而在 C 中,函数是重要的。这就是为什么我写了“相信语言”的原因。为了在 C++ 中正确地工作,你需要从“过程式”的视角转换到“对象应该如何行为”的视角。如果没有这个转换,那么你所看到的都是敌对的代码。 - paercebalstd::endl
?),它可能会导致更多的浪费。在C++中,你必须走过一个奇怪的中间地带,关心一些细节而忽略其他细节,而在C中,陷阱所在更加清晰。 - cHaoa + b
或a = b
或a(b)
就是它看起来的样子,并且不是一个巨大的操作,并且不会根据它所操作的内容而改变其含义。 - cHaoC99 中被忽略的一个特性是 VLA,而 C++ 据说没有相应的功能。
甚至有人质疑在 C++ 中编写类似 VLA 的对象是否可能。
这就是我添加这个答案的原因:尽管它略微偏离主题,但仍然表明,通过正确的库,C++ 开发人员仍然可以访问模仿 C99 特性的对象。因此,这些特性并不像人们想象的那样被忽视。
主要代码如下:
#include <iostream>
#include <string>
#include "MyVLA.hpp"
template <typename T>
void outputVLA(const std::string & p_name, const MyVLA<T> & p_vla)
{
std::cout << p_name << "\n MyVla.size() : ["
<< p_vla.size() << "]\n" ;
for(size_t i = 0, iMax = p_vla.size(); i < iMax; ++i)
{
std::cout << " [" << i << "] : [" << p_vla[i] << "]\n" ;
}
}
int main()
{
{
MY_VLA(vlaInt, 5, int) ;
outputVLA("vlaInt: Before", vlaInt) ;
vlaInt[0] = 42 ;
vlaInt[1] = 23 ;
vlaInt[2] = 199 ;
vlaInt[3] = vlaInt[1] ;
vlaInt[4] = 789 ;
outputVLA("vlaInt: After", vlaInt) ;
}
{
MY_VLA(vlaString, 4, std::string) ;
outputVLA("vlaString: Before", vlaString) ;
vlaString[0] = "Hello World" ;
vlaString[1] = "Wazaabee" ;
vlaString[2] = vlaString[1] ;
vlaString[3] = "Guess Who ?" ;
outputVLA("vlaString: After", vlaString) ;
}
}
正如您所见,MyVLA对象知道其大小(这比在C99 VLAs上使用sizeof
运算符要好得多)。
当然,MyVLA类的行为就像一个数组,并且由size_t值初始化(可以在运行时更改)。唯一的问题是由于alloca()
函数的性质,这意味着构造函数只能通过宏MY_VLA间接使用:
下面是文件MyVLA.hpp中该类的代码:
#include <alloca.h>
template <typename T>
class MyVLA
{
public :
MyVLA(T * p_pointer, size_t p_size) ;
~MyVLA() ;
size_t size() const ;
const T & operator[] (size_t p_index) const ;
T & operator[] (size_t p_index) ;
private :
T * m_begin ;
T * m_end ;
} ;
#define MY_VLA(m_name, m_size, m_type) \
m_type * m_name_private_pointer = static_cast<m_type *>(alloca(m_size * sizeof(m_type))) ; \
MyVLA<m_type> m_name(m_name_private_pointer, m_size)
template <typename T>
inline MyVLA<T>::MyVLA(T * p_pointer, size_t p_size)
{
m_begin = p_pointer ;
m_end = m_begin + p_size ;
for(T * p = m_begin; p < m_end; ++p)
{
new(p) T() ;
}
}
template <typename T>
inline MyVLA<T>::~MyVLA()
{
for(T * p = m_begin; p < m_end; ++p)
{
p->~T() ;
}
}
template <typename T>
inline size_t MyVLA<T>::size() const
{
return (m_end - m_begin) ;
}
template <typename T>
inline const T & MyVLA<T>::operator[] (size_t p_index) const
{
return *(m_begin + p_index) ;
}
template <typename T>
inline T & MyVLA<T>::operator[] (size_t p_index)
{
return *(m_begin + p_index) ;
}