可以用c而不是c ++做什么?
有什么东西可以在C中完成,但在C ++中却没有,在C ++编码时你最想念的是哪些?
我能想到的几件事情:
- 我们可以将任何类型的指针分配给void指针,而不是在c中而不是在c ++中。
- 声明变量名称是C ++中的关键字但不是C;)
编辑:谢谢@sbi指出:
1.应该是:我们可以为C中的任何类型的指针指定void指针,但不能在C ++中指定
注意:我想我会因此而受到抨击,但是,这是C ++开发人员的C ++问题,所以……
有什么东西可以在C中完成,但在C ++中却没有,在C ++编码时你最想念的是哪些?
作为一名C ++开发人员,我不会错过C,无论是C99还是其他。
我不是出于好意而写的。 对于缺少C / C99function的C ++开发人员来说,这是一个问题,因为他们忽略了C ++的基本function 。 我相信这个问题及其答案忽略了C ++中可行的或更好的替代方案(不,“C ++向量是令人讨厌的”评论只是一个虚假的原因)。
这就是为什么我会在这里讨论所谓的“缺失特征”……
可变长度数组?
可变长度数组是C99的语言特性。 它的主要优点是:
- 在堆栈上分配
- 创作时可变长度
- 无需解除分配
对于最常见的情况, std::vector
将完成工作,并且无论如何都有更多function。 例如,除非我错了,可变长度数组具有以下缺点:
- 堆栈上的分配意味着您无法从声明它的函数返回VLA
- VLA无法resize,这意味着如果它太小,那么你就搞砸了
- VLA必须在原型范围或块范围内声明。 它不能是外部的,也不是静态的。 并且您不能将其声明为结构的成员。
- 它不能有初始化程序
矢量可以resize,并可以返回。 使用C ++ 0x(和r值引用),您可以使用移动语义返回向量,这意味着不需要无用的临时对象。 您可以将它放在struct / class中,它可以是extern或static。 您可以使用默认值,数组内容,容器或带有C ++ 0x的初始化列表初始化它。
即使在那之后,如果你真的想要像VLA这样的东西,在C ++中,普通的C ++开发人员可以编写一个基于堆栈的类似矢量的容器。 并且它不需要完整的语言委员会更新。
只是为了好玩,我碰巧发布了一个简单的C ++ VLA类概念validation的答案 。
C ++的向量在大多数情况下是更好的选择,具有更多function。 在极少数情况下,确实需要VLA,其function可以由用户定义的类模拟。
将void *
转换为T *
?
至于将任何void *
转换为另一个类型指针,这不是C ++缺少C的特性: 这是弱类型与强类型的选择
并且它不像在C ++中这样做是不可能的,因为你可以使用强制转换来完成它。 这种差异的关键在于降低一种语言中的bug风险,其中void *
不像另一种语言那样有用:在我目前的C ++ 100k行项目中,我没有出现void *
。
指定的初始化器?
构造函数提供了更好的选择。
当然,你没有直接初始化结构中数据的可能性,但是,数据封装意味着大多数时候,我的对象中的数据是私有的,所以,使用指定初始化器初始化的整个概念他们会很荒谬。
对于类似POD的结构,构造函数很容易编写,并且可以处理指定初始化程序永远不会执行的情况(比如默认情况下初始化具有非零值的成员,甚至调用函数)。
由于C ++专注于数据封装,因此构造函数为指定的初始化器提供了更好的替代方案。
编辑2011-11-05:
在重新阅读本节之后,我想澄清一点:指定的初始化程序对于非常有限的情况(即POD)非常有用,这意味着虽然我不会错过它们(如问题中所述),但我不介意拥有它们。
复合文字?
这种语法糖再次假设您既知道结构的确切实现,又拥有对其成员的公共访问权限,这是您通常希望在C ++中避免的内容。
如上所述 , 复合文字不再是function,方法甚至构造函数无法处理的东西,具有奖励function的优点 。
声明变量名称是C ++中的关键字而不是C?
我知道你的感受:每当我有可能在C ++中使用interface
, final
或synchronized
时,我也会感到Java颤抖……
😛
类型通用宏?
C中的问题是你有很多函数,对不同的类型执行相同的语义操作,这意味着每个函数必须具有不同的名称。 例如,根据OpenGroup,存在以下function:
-
double sin(double x);
-
float sinf(float x);
-
long double sinl(long double x);
- 等等
资料来源: http : //www.opengroup.org/onlinepubs/009695399/functions/sin.html
但他们的名字是一个真正的痛苦要记住,所以有人有一个想法。 关于宏的一些内容,它将根据所使用参数的类型使用编译器内置扩展来调用正确的扩展。
这是C99的
的
。
这个想法看起来很棒 ,以至于他们甚至为下一个C标准中的所有function添加了一个命题来提供这个function,例如:
#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
现在,令人震惊的消息:几十年来,这个function在C ++中可用:这称为函数重载。
例如,上面的函数在C ++中声明为:
-
double sin (double x );
-
float sin (float x );
-
long double sin (long double x );
- 等等
因此,“Type-generic宏”是一个被黑客攻击的实现,它致力于(部分地)模拟更通用的C ++函数重载。
并猜测:您甚至可以为自己的用户定义类型添加自己的重载。
结论
如上所示,每次我研究C99特征时,结论都是:“嘿,我已经可以用C ++做到了!” (通常在句子的某处用“更好”这个词)。
说真的,作为一名C ++开发人员,我现在想念的是能够在工作中使用C ++ 0x。 例如,以下C ++ 0xfunction:
-
auto
-
constexpr
- 初始化列表
- r值参考
- lambda表达式
-
nullptr
- 等等
整个“C缺少来自C ++的特性”是一个被高估的概念,我怀疑,C开发人员(和C-with-classes开发人员)比C ++开发人员更有趣。
您可能会发现ISO C和ISO C ++之间的网页不兼容性很有趣。
我大多错过了许多C ++中没有的C99function:
- 复合文字;
- 指定的初始化器;
- 变量参数宏(包含在C ++ 0X中)。
不是C中的一个特性,但可以说是C的一个杀手级特性,C89的简单语法使得编写编译器变得容易。 好吧,无论如何,与C ++编译器相比。
- 我们可以将任何类型的指针分配给void指针,而不是在c中而不是在c ++中。
任何指针也可以在C ++中转换为void*
。 你正在以这种方式丢失信息,编译器也不会阻止你这样做。 这是一个问题的反面,因为这样你就获得了编译器无法检查的信息。
我认为C允许这样做,而C ++肯定没有。
如果我们忽略了显而易见的差异来源–C99 – 将自己限制在C89 / 90,并且还丢弃了像C ++关键字这样的平凡变体,那么C和C ++之间仍然会有一些差异
(1)您已经提到了在没有强制转换的情况下将void *
转换为任何具体指针类型的能力。
(2)具有“未指定”参数的函数类型,即函数类型声明中的()
。 在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)数组初始化与字符串文字的一些区别:尾随\0
允许在C中脱落,但在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)如果我记得它,我会稍后添加其他一些东西。
您可以在C中使用可变长度数组 ,但不能在C ++中使用。 我相信这将非常有用,而不是为此做一个new[]
。
句法糖和类型滥用有一些微妙的差异,但它们很容易解决。
C最重要的function是生成完全独立的程序,完全没有外部依赖性。 这就是为什么操作系统内核几乎普遍用C语言编写的原因.C实际上是为实现操作系统而设计的。 可以在C ++的受限子集中编写OS内核,但强制执行这些限制只发生在链接时,如果有的话,那么处理它比处理次要语法差异要困难得多。
在C中,您可以隐式地在void指针和其他指针之间进行转换,但您必须在C ++中进行显式转换。
void* 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++
在C中我喜欢的是能够说出类似a = b;
东西a = b;
并确切地知道它在做什么。 在C ++中,任何人都可以覆盖运算符,这意味着像这样的简单语句可能最终会调用一些大型复制构造函数(或者更糟糕的是,完全不相关的东西)。 你看a = b;
在C ++中,你必须猜测(或者去查找)是否有人这样做只是为了讨厌。
作为C的粉丝,我认为编码风格的概念可能还有另外一点,那就是函数式编程,而不是面向对象的编程,称为OOP! 对于那些将代码编写为状态机的人来说,这是一个非常有趣的概念! 考虑将opengl作为一个很好的例子。 由于较少的内存引用,在C中运行代码的速度也非常好。 您可能也喜欢听到许多程序员喜欢用C语言编写代码,因为它们的设计简单,以及如何使用它们(语法可能不那么容易)。 哦,当你想编写非常接近硬件级别的代码时,你应该使用纯C.
C99错过的function之一是VLA,C ++应该没有相应的function。
有些人甚至质疑在C ++中写一个类似VLA的对象的可能性。
这就是为什么我添加了这个答案:尽管稍微偏离主题,它仍然表明,使用正确的库,C ++开发人员仍然可以访问模仿C99function的对象。 因此,这些特征并不比想象的那么简单。
主要代码是:
#include #include #include "MyVLA.hpp" template void outputVLA(const std::string & p_name, const MyVLA & 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 VLA上使用sizeof
运算符要好得多)。
当然,MyVLA类的行为类似于数组,并且由size_t值初始化(可以在运行时更改)。 唯一的故障是由于函数alloca()
的性质,这意味着构造函数应该只通过宏MY_VLA间接使用:
下面是该类的代码,在文件MyVLA.hpp中
#include template 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(alloca(m_size * sizeof(m_type))) ; \ MyVLA m_name(m_name_private_pointer, m_size) template inline MyVLA::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 inline MyVLA::~MyVLA() { for(T * p = m_begin; p < m_end; ++p) { p->~T() ; } } template inline size_t MyVLA::size() const { return (m_end - m_begin) ; } template inline const T & MyVLA::operator[] (size_t p_index) const { return *(m_begin + p_index) ; } template inline T & MyVLA::operator[] (size_t p_index) { return *(m_begin + p_index) ; }
宏是一团糟,可能写得更好。 这个类本身可能不是exception安全的,但它可以成为。 无论如何,它需要更多代码才能使用(即处理复制/赋值,使新/删除私有,尽可能添加const
等)。 我的猜测是,结束并不能certificate我花在它上面的时间。 所以它仍将是一个概念validation。
关键是“C ++可以模拟C99 VLA,它甚至可以作为C ++对象数组工作!”,我想我成功地certificate了这一点。
我让读者复制 - 粘贴 - 编译代码以查看结果(我在Ubuntu 10.04上的g ++ 4.4.3上编译它。
在C中,您可以定义要删除的变量名称。 你不能用C ++做到这一点。
C和C ++之间的主要区别在于C ++是面向对象的,而C是面向函数或面向过程的。 面向对象编程范例专注于编写更易读和可维护的程序。 它还通过打包一组类似对象或使用组件编程模型的概念来帮助重用代码。 它通过使用对象,inheritance和多态的现实世界概念的概念,帮助以逻辑方式进行思考。 应该注意的是,这些特征也存在一些缺点。 例如,在程序中使用多态可能会降低该程序的性能。
另一方面,function和过程编程主要关注动作和事件,编程模型侧重于触发程序代码执行的逻辑断言。