c++真的只是增加了类的c吗?原来c99与c++98的不同点有这么多,一起来学习一下吧。
本文是一篇读书笔记。对原文的部分翻译。原文见末尾
本文原作者是David R. Tribble
注意本篇文章只讨论那些在c中有,但与c++不同的地方,c++独有而c中没有的如类,类的成员函数等不在讨论之列
以下这些在c99中已经改为与c++98兼容了,不用在意
(这一部分内容基本都省略不在这写了,只有段落分隔符这一个从没见过,单列一下,实际上也没人这么写)
<: [
:> ]
<% {
%> }
%: #
%:%: ##
这样一来就能写出这样的代码。。。
%:include
%:ifndef BUFSIZE
%:define BUFSIZE 512
%:endif
void copy(char d<::>, const char s<::>, int len)
<%
while (len-- >= 0)
<%
d<:len:> = s<:len:>;
%>
%>
and &&
and_eq &=
bitand &
bitor |
compl ~
not !
not_eq !=
or ||
or_eq |=
xor ^
xor_eq ^=
c89中没有这些关键字, c语言通过头文件
数组参数限定符Array parameter qualifiers
c99 支持在数组参数前加类型限定符 const volatile restrict static, 此特性C++完全不支持
extern void foo(int str[const]); // 含义同 extern void foo(int *const str);
void baz(char s[static 10]){
// s[0] thru s[9] exist and are contiguous
}
布尔类型Boolean type
c99支持_Bool关键字 还提供了stdbool.h来支持 bool true false,如果不包含stdbool.h,可任意使用这些名字,造成不兼容
c++没有stdbool.h 且 bool true false 是关键字
字符常量Character literals
c中’a’ 的类型是int, 而在c++中 类型是 char
c99中在math.h将其定义为 计算复数的自然对数 的函数名
c++98中在iostream 中定义了 std::clog 作为标准错误日志输出流 与 std::err类似。
(此条在vs2019中math.h中没有clog的定义,vs把clog定义在在complex.h中了, gcc 9.3中也math.h也没有clog定义了,只是需要知道,有clog这么个东西可能冲突,用到再注意就行了)
在c中 逗号操作符 总是产生右值结果,在c++中如果逗号右侧是左值,则会返回左值结果
(i, j) = 1; // C++中有效, C 中无效 // 在gcc 9.3中实测全都是有效
C99 中关键字_complex _Imaginary, 又有complex.h定义了complex imaginary以便使用。
C++ 的complex头文件中 定义了complex 模板
使用时可以按以下方法typedef一下这样怎么用都不会错了
#ifdef __cplusplus
#include
typedef complex complex_float;
typedef complex complex_double;
typedef complex complex_long_double;
#else
#include
typedef complex float complex_float;
typedef complex double complex_double;
typedef complex long double complex_long_double;
typedef imaginary float imaginary_float;
typedef imaginary double imaginary_double;
typedef imaginary long double imaginary_long_double;
#endif
extern void add(struct info s);
add((struct info){ "e", 0 }); // (struct info){ "e", 0 }就是一个复合字面量,前面是类型,后面是初始化值
c99允许此特性 c++不允许
c中const变量不可改变,默认是extern的,除非指定static,即const int x = 1就等于extern const x = 1
c++ 中const变量默认是本文件可见,默认不带extern
建议是用的时候明确指定extern、 static
初始化结构体时可以用 .成员名字的方式初始化,否则要按声明顺序来初始化。
struct info
{
char name[8+1];
int sz;
int typ;
};
struct info arr[] =
{
[0] = { .sz = 20, .name = "abc" }, // 注意以.sz .name来直接指定初始化
[9] = { .sz = -1, .name = "" }
};
C++中不支持此特性
c中不允许同样的typedef出现2次以上,c++中无此限制,
主要影响是在头文件中的typdef,如果被重复包含在c中会报错,解决方法是
#ifndef xxx #define xxx typdef int myint; #endif
c支持变长数组sizeof(VLA_arr)会在运行时求值,在c++中不支持变长数组sizeof需要在编译器求值sizeof(VLA_arr)会报错
对于函数 void foo(void) C和C++都认为这是一个不接受任何参数的函数
对于函数 void foo() C中认为这是一个接受不定个数参数的函数,C++中认为这是与void foo(void)一样不接受任何参数的函数
#define ADD3(a,b,c) (+ a + b + c + 0)
ADD3(1, 2, 3) => (+ 1 + 2 + 3 + 0)
ADD3(1, 2, ) => (+ 1 + 2 + + 0)
ADD3(1, , 3) => (+ 1 + + 3 + 0)
ADD3(1,,) => (+ 1 + + + 0)
ADD3(,,) => (+ + + + 0)
c99允许ADD()中的参数为空 , C++不支持这么干 (目前VS2019 GCC 9.3都支持这么干了)
C中枚举常量就是起了名的singed int变量,其值肯定在[INT_MIN, INT_MAX]中
C++ 中枚举常量类型可以为signed int, unsigned int, signed long, unsigned long.
(这里需要注意long类型在vs2019中4字节大小,gcc9.3中为8字节大小),依赖于size(enum_x)的代码将可能遇到问题
enum Color { RED = 0, GREEN, BLUE, }; // BLUE后面的逗号在C++不允许
(vs2019 gcc9.3现在都允许了)
C中枚举的类型不同,C标准允许其对不同枚举使用不同的底层整数类型,即sizeof(enumA) 不一定与 sizeof(enumB)相同,但在使用时都会转为signed int所以可以不用显示转换,即
enum Color { RED, BLUE, GREEN }; // sizeof(RED) 也可能与 sizeof(Color)不同。。。
enum Color2 { RED2, BLUE2, GREEN2 };
int c = RED; // 不用显示式转换
enum Color col = 1; // 不用显示转换
void func(enum Color x); // 会报冲突
void func(enum Color2 x);// 会报冲突
c++中对类型区分更细,上面的func能正常编译,但枚举与整数之间需要显示转换
enum Color { RED, BLUE, GREEN };// sizeof(RED) 与 sizeof(Color)相同
enum Color2 { RED2, BLUE2, GREEN2 };
int c = (int)RED; // 要显示式转换
enum Color col = (Color)1; // 要显示转换
void func(enum Color x); // ok
void func(enum Color2 x);//ok
c语言结构体后面可以跟一个不定长数组,用来传一些东西,C++不支持这么做
struct Hack
{
int count; // Fixed member(s)
int fam[]; // Flexible array member
};
struct Hack * vmake(int sz)
{
struct Hack * p;
p = malloc(sizeof(struct Hack) + sz*sizeof(int)); // 分配一个hack和一个整数数组的内存
p->count = sz;
for (int i = 0; i < sz; i++)
p->fam[i] = i; // 注意这里的使用方式
return p;
}
编译时编译器会根据函数名字 参数 返回值等信息 生成唯一的名字,以便后续链接等使用,而这个名字的生成C和C++采用的策略不同。所以如果混用,会发生符号链接失败的情况。解决方案是正确使用extern “C”
而且c++mangling时会用到两个下划线,故不允许函数名字中连续两个下划线(vs2019 gcc9.3 无此限制)
C++函数默认都是extern "c++"链接,使用C函数时必须extern “C”,但如果在C++中定义了指向函数的指针,此指针不能指向extern "C"的函数,
如果想用指向C函数的指针,需要把指针也extern “C”
float pi = 0x3.243F6A88p+03;
printf("%9.3a", f);
printf("%12.6lA", d);
c++中不太支持这些写法 (vs2019 gcc9.3均支持)
C99允许编译器提供预定义__STD_IEC_559 __STD_IEC_559_COMPLEX,以标示其对IEC 60559的支持。(vs2019 gcc9.3好像还没有这个预定义)
C++会要求内联函数的定义完全一致,而C不会这样要求。
如果在两个c文件中定义同的内联函数不一样如inline int func(int)和inline myint func(myint),C++会报错,但实际使用中内联函数一般都在头文件中,不会有此问题。
c99提供了
C++库头文件会把C里的一些函数改为类型安全的函数,可能会造成报错
// c
extern char * strchr(const char *s, int c);
// c++
extern const char * strchr(const char *s, int c);
extern char * strchr(char *s, int c);
在使用strchr时若想在c和c++中均正确可以加上类型强制转换char * p = (char *) strchr(s, ‘a’);
C++中包含了C89的头文件,但基本也提供了对应的C++版本头文件,如math.h --> cmath
c99增加了
c99增加了signed long long 和unsigned long long类型,并且也支持如下写法
long long int i = -9000000000000000000LL;
unsigned long long int u = 18000000000000000000LLU;
printf("%lld", i);
c++ 不支持这些类型 (vs2019 gcc9.3已经支持了)
Nested structure tags
C中如果在结构体内部声明其它结构、枚举,则其在结构体外也可见。C++内部声明只在内部可见
非原型函数声明 Non-prototype function declarations
C语言还支持K&R式函数声明 int foo(a,b) int a; int b; { return a + b;}
C++不提供此种支持
C++ 最好使用const_cast dynamic_cast reinterpret_cast static_cast,C++还支持函数式的类型转换float f = float(i),C语言不支持这些
C语言允许尝试性定义,C++不允许
int i;
int i=3;
不同源文件之中的函数、结构体定义,C语言不会检查是否一致,C++要求必须完全一致(与前面inline function相同)
_Pragma关键字_Pragma keyword
C99 支持_Pragma 关键字_Pragma(FLT_XXX) 与#pragma FLT_XXX相同
预定义标识符 Predefined identifiers
c99 支持__func__ ,C++不一定提供,且若在内部命名空间中的内部模板类的成员函数中(member functions within nested template classes declared within nested namespaces)使用时,其值不知是什么。
C99保留关键字Reserved keywords in C99
c99有几个特殊保留关键字restrict _Bool _Complex _Imaginary _Pragma c++可能不认识
extern int set_name(char *restrict n); 编译含有这些关键字代码时可能会报错
bool mutable this
catch namespace throw
class new true
const_cast operator try
delete private typeid
dynamic_cast protected typename
explicit public using
export reinterpret_cast virtual
false static_cast wchar_t
friend template
以及c++保留asm关键字。C代码中如果使用了这些关键字,使用C++编译就会报错
C99支持restrict 允许对指针的特定优化,C++不认识此关键字
返回void Returning void
C++ 允许返回void型表达式,而在C中标识了返回值void就不允许返回任何东西
static的链接static linkage
C和C++中static的链接都是internal linkage,但在c++中推荐把代码放在匿名空间中以达到相同效果
字符串文本是const String literals are const
c中字符串文档类型是 char[n] 但不能修改 c++中类型是const char[n],把字符串文本当参数传给函数时,C中转换为char* C++中转换为const char* ,
extern void frob(char* s);//
void foo(void)
{
frob("abc"); // 在c++中这里会报错 (vs2019 gcc9.3)
}
c 允许在函数原型中声明struct union enum 如
extern void foo(const struct info { int typ; int sz; } *s);
也允许返回值中声明struct 如
extern struct pt { int x; } pos(void);
但C++不允许这样。可以将以上几个声明改成一个struct指针,这样C和C++都支持
C99通过tgmath.h支持通用的数学函数如 sin(x) 实际对应的 sin(float x) sin(double x) sin(long double x)
sin(float complex x) sin(double complex x) sin(long double complex x)
C++也可能提供了,但需要注意 指向这些通用函数的指针 应会有不同的表现
C的type tags需要和前面的strut union enum组合在一起才有效
C++ 将type tags当成隐式的typdef ,所以会有以下情况
// 这段代码C中能编译 c++中编译不过 (gcc9.3确实如此
typedef int type;
struct type
{
type memb; // int
struct type * next; // struct pointer
};
void foo(type t, int i)
{
int type;
struct type s;
type = i + t + sizeof(type);
s.memb = type;
}
下面这段代码在c c++中都能编译但结果不同
int sz = 80;
int size(void)
{
struct sz
{ ... };
return sizeof(sz); // sizeof(int) in C,
// sizeof(struct sz) in C++
}
变长参数函数声明Variable-argument function declarators
C++和C都支持变长参数函数声明 但C++可以省略…前面的逗号
int foo(int a, int b, …); // Valid C++ and C
int bar(int a, int b …); // Valid C++, invalid C
变长参数预处理函数宏 Variable-argument preprocessor function macros
C99支持宏中的…,C++不支持 (gcc 9.3已经支持了)
#define DEBUGF(f,...) \
(fprintf(dbgf, "%s(): ", f), fprintf(dbgf, __VA_ARGS__))
#define DEBUGL(...) \
fprintf(dbgf, __VA_ARGS__)
int incr(int *a)
{
DEBUGF("incr", "before: a=%d\n", *a);
(*a)++;
DEBUGL("after: a=%d\n", *a);
return (*a);
}
变长数组 Variable-length arrays (VLAs)
c99支持变长数组 extern float sum_square(int n, float a[*]); float sum_cube(int n, float a[m])
注意的是 * 只允许出现在函数声明中 不允许定义中
C++ 并不支持VLAs
void指针赋值 Void pointer assignments
C允许void直接赋给其它 指针,不需要类型转换
c++ void必须强制类型转换才能赋给其它指针。
但将一个指针 赋值给void* p,在C和C++中都不需要强转
原文地址经常打不开,可能需要。