0.前置知识

C++语言体系

C  /C#基本数据类型对比分析 - 图2

C++语言风格和特性

C++ 类型系统
C++ 是强类型 语言,也是静态类型化语言;每个对象都有一个类型,并且该类型永远不会更改 (不会与静态数据对象混淆) 。 在代码中声明变量时,必须显式指定其类型,或使用 auto 关键字指示编译器从初始值设定项中推断该类型。

C# 语言风格和特性

C# 是一种强类型语言。编译器使用类型信息来确保在代码中执行的所有操作都是类型安全的。 例如,如果声明 int 类型的变量,那么编译器允许在加法和减法运算中使用此变量。 如果尝试对 bool 类型的变量执行这些相同操作,则编译器将生成错误。
当在程序中声明变量或常量时,必须指定其类型或使用 var 关键字让编译器推断类型。

C++程序书写位置

头文件、源文件:理解 C++ 中的头文件和源文件的作用

  • C++ 语言支持”分别编译”(separatecompilation)。也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的 .cpp 文件里。.cpp 文件里的东西都是相对独立的,在编译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目标文件做一次链接(link)就行了。
  • 在文件 b.cpp 中,在调用 “void a()” 函数之前,先声明一下这个函数 “voida();”,就可以了。这是因为编译器在编译 b.cpp 的时候会生成一个符号表(symbol table),像 “void a()” 这样的看不到定义的符号,就会被存放在这个表中。再进行链接的时候,编译器就会在别的目标文件中去寻找这个符号的定义。一旦找到了,程序也就可以顺利地生成了。
  • 我们把所有的函数声明全部放进一个头文件中,当某一个 .cpp 源文件需要它们时,它们就可以通过一个宏命令 “#include” 包含进这个 .cpp 文件中,从而把它们的内容合并到 .cpp 文件中去。当 .cpp 文件被编译时,这些被包含进去的 .h 文件的作用便发挥了。
  • 简单来说:头文件中应该只放变量和函数的声明,而不能放它们的定义。其他程序需要调用时,只需要包含头文件就可以了。

【与C#比较】:C#程序可以存储在多个源文件中。 在编译 C# 程序时,将同时处理所有源文件,并且源文件可以自由地相互引用。 从概念上讲,就好像所有源文件在被处理之前都连接到一个大文件。 在 C# 中永远都不需要使用前向声明,因为声明顺序无关紧要(极少数例外情况除外)。

1.数据类型简介和检测

数据类型是程序的基础:它告诉我们数据的意义以及我们能在数据上执行的操作。变量是在内存中存储值的符号。

1.1 数据类型分类

C++类型

参考:C++ 类型系统
C  /C#基本数据类型对比分析 - 图3
不同于某些语言,C++ 中不存在派生所有其他类型的通用基类型。
C++包括许多 基本类型,也称为 内置类型基本类型由编译器识别,编译器包含内置规则,这些规则管理可对它们执行的操作以及将它们转换为其他基本类型的方式。
基本类型包括整数类型、浮点类型、字符类型、布尔类型、void类型以及std::nullptr_t。
其他类型还有:字符串类型、指针类型、用户自定义类型。

C# 类型

参考:C# 类型系统
C# 是面向对象的、面向组件的编程语言。
和C++一样,C#类型使用内置和自定义的区别可以分类为:
C  /C#基本数据类型对比分析 - 图4
对于 .NET 中的类型系统,有两个基本要点:

  • 它支持继承原则。 类型可以派生自其他类型(称为基类型)。 所有 C# 类型(包括 int 和 double 等基元类型)均继承自一个根 object 类型。 所有类型共用一组通用运算。 任何类型的值都可以一致地进行存储、传输和处理。这样的统一类型层次结构称为通用类型系统 (CTS)。
  • CTS中每种类型被定义为值类型或引用类型。引用类型和值类型遵循不同的编译时规则和运行时行为。

C  /C#基本数据类型对比分析 - 图5
按照值和引用类型的区别可以分类为:
C  /C#基本数据类型对比分析 - 图6
C# 中的值类型引用类型

  • 值类型的变量直接包含它们的数据,每个变量都有自己的数据副本;因此,对一个变量执行的运算不会影响另一个变量(ref 和 out 参数变量除外)。
  • 引用类型的变量存储对数据(称为“对象”)的引用。 对于引用类型,两个变量可以引用同一个对象;对一个变量执行的运算可能会影响另一个变量引用的对象。

C# 的值类型进一步分为:简单类型、枚举类型、结构类型、可以为 null 的值类型和元组值类型。
C# 引用类型又细分为类类型、接口类型、数组类型和委托类型。

1.2 数据类型判断

C++

C++中的typeid是由C++标准库提供,定义于头文件,用于判断某个变量的类型。

  1. #include<typeinfo>
  2. #include<iostream>
  3. using namespace std;
  4. int main()
  5. {
  6. int i;
  7. cout << typeid(i).name() << "\n";
  8. //输出结果为i(表示int),即类型的名称首字母
  9. return 0;
  10. }

typeof由编译器提供(目前仅gcc编译器支持),用于返回某个变量或表达式的类型。不用知道函数返回什么类型,可以使用typeof()定义一个用于接收该函数返回值的变量。

  1. typeof(get_apple_info()) r1;

对于C语言,可以使用sizeof来判断

  1. #include<stdio.h>
  2. int main(){
  3. int var;
  4. char var_char;
  5. var = 1;
  6. var_char = '1';
  7. printf("int类型1占位:%d\n",sizeof(var)); //int类型1占位:4
  8. printf("char类型1占位:%d\n",sizeof(var_char));//char类型1占位:1
  9. return 0;
  10. }

C

使用对象的 GetType 函数,获取对象类型。还可以获取类型的fullName。
使用 typeof 函数,获取已知类型。
使用 is 关键字,判断对象类型。

  1. int i = 5;
  2. //方法一:
  3. Console.WriteLine( "i is an int? {0}",i.GetType()==typeof(int));
  4. //方法二:
  5. Console.WriteLine( "i is an int? {0}",typeof(int).IsInstanceOfType(i));
  6. //方法三:
  7. var isB = oldObject.GetType().FullName.IndexOf("Dictionary") > 0;
  8. //方法四:
  9. var isC = oldObject is Dictionary<string, string>;

2.基本数据类型分析

C++

C  /C#基本数据类型对比分析 - 图7

整型

大多数整型基本类型有unsigned版本,这些版本修改变量可以存储的值范围。 例如,存储 32 位带符号整数的一个 int值可以表示从 -2,147,483,648 到 2,147,483,647 的值。 一个 unsigned int(也存储为 32 位)可以存储从 0 到 4,294,967,295 的值。 可能的值的总数在每种情况下都相同;仅范围不同。

浮点型

类型 目录
float 类型 float 是 C++ 中最小的浮点类型。
double double 类型是大于或等于 float类型的大小但小于或等于 long double类型的大小的浮点类型。
long double long double 类型是大于或等于 double类型的浮点类型。

字符类型

char 类型是一种字符表示类型,可有效地对基本执行字符集的成员进行编码。 C++ 编译器将 char, signed charunsigned char 类型的变量视为不同类型。
类型的 wchar_t 变量是宽字符或多字节字符类型。 使用 L 字符或字符串文本前的前缀指定宽字符类型。
字符类型的算术运算,实际上是使用ASCII数字进行运算。

  1. cout<<'a'+'b'<<endl; //195

布尔类型

只有true和false2种值。

std::nullptr_t

关键字 nullptr 是类型的 std::nullptr_t null 指针常量,可转换为任何原始指针类型。

内置类型大小

大多数内置类型具有实现定义的大小。 下表列出了 Microsoft C++ 中内置类型所需的存储量。

类型 大小
bool, char, char8_t, unsigned char, signed char, __int8 1 个字节
char16_t, __int16, short, unsigned short, wchar_t, __wchar_t 2 个字节
char32_t, float, __int32, int, unsigned int, long, unsigned long 4 个字节
double, __int64, long double, long long, unsigned long long 8 个字节

C  /C#基本数据类型对比分析 - 图8

string类型

严格地说,C++ 语言没有内置字符串类型: charwchar_t 存储单个字符,必须将这些类型的数组声明为近似字符串, (添加终止 null 值,例如,ASCII ‘\0’) 数组元素。
C 样式字符串需要编写更多的代码或者需要使用外部字符串实用工具库函数。 但在现代 C++ 中,我们有标准库类型 std::string (8 位 char类型字符串) 或 std::wstring 16 位 wchar_t类型字符串) 。 这些 C++ 标准库容器可以被视为本机字符串类型,因为它们是任何符合 C++ 生成环境中包含的标准库的一部分。 只需使用 #include 指令即可使这些类型在你的程序中可用。 (如果使用 MFC 或 ATL,则 CString 类也可用,但不属于 C++ 标准。)

C

值类型分为两类:struct和enum。内置的数值类型是结构,它们具有可访问的字段和方法。
C  /C#基本数据类型对比分析 - 图9

整型

C# 类型/关键字 范围 大小 .NET 类型
sbyte -128 到 127 8 位带符号整数 System.SByte
byte 0 到 255 无符号的 8 位整数 System.Byte
short -32,768 到 32,767 有符号 16 位整数 System.Int16
ushort 0 到 65,535 无符号 16 位整数 System.UInt16
int -2,147,483,648 到 2,147,483,647 带符号的 32 位整数 System.Int32
uint 0 到 4,294,967,295 无符号的 32 位整数 System.UInt32
long -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 64 位带符号整数 System.Int64
ulong 0 到 18,446,744,073,709,551,615 无符号 64 位整数 System.UInt64
nint 取决于平台 带符号的 32 位或 64 位整数 System.IntPtr
nuint 取决于平台 无符号的 32 位或 64 位整数 System.UIntPtr

关键字(.NET类型的别名)和 .NET 类型名称是可互换的。 例如,以下声明声明了相同类型的变量:

  1. int a = 123;
  2. System.Int32 b = 123;

表的最后两行中的 nint 和 nuint 类型是本机大小的整数。
每个整型类型的默认值都为零 0。除本机大小的类型外,每个整型类型都有 MinValue 和 MaxValue 常量,提供该类型的最小值和最大值。
初始化使用整数文本。整数文本可以是

  • 十进制:不使用任何前缀
  • 十六进制:使用 0x 或 0X 前缀
  • 二进制:使用 0b 或 0B 前缀(在 C# 7.0 和更高版本中可用)
    1. var decimalLiteral = 42;
    2. var hexLiteral = 0x2A;
    3. // _ 用作数字分隔符(从 C# 7.0 开始提供支持),用于所有类型的数字文本。
    4. var binaryLiteral = 0b_0010_1010;
    整数文本的类型由其后缀确定,可以使用的后缀有:L/u/U/UL….,不使用后缀会自动按一定顺序来推断。

    浮点型

    | C# 类型/关键字 | 大致范围 | 精度 | 大小 | .NET 类型 | | —- | —- | —- | —- | —- | | float | ±1.5 x 10−45 至 ±3.4 x 1038 | 大约 6-9 位数字 | 4 个字节 | System.Single | | double | ±5.0 × 10−324 到 ±1.7 × 10308 | 大约 15-17 位数字 | 8 个字节 | System.Double | | decimal | ±1.0 x 10-28 至 ±7.9228 x 1028 | 28-29 位 | 16 个字节 | System.Decimal |

每个浮点类型的默认值都为零0。 每个浮点类型都有 MinValue 和 MaxValue 常量。
float and double 类型还提供可表示非数字和无穷大值的常量。 例如,double 类型提供以下常量:Double.NaNDouble.NegativeInfinityDouble.PositiveInfinity
初始化使用真实文本。真实文本的类型由其后缀确定,如下所示:

  • 不带后缀的文本或带有 d 或 D 后缀的文本的类型为 double
  • 带有 f 或 F 后缀的文本的类型为 float
  • 带有 m 或 M 后缀的文本的类型为 decimal ```csharp double d = 3D; d = 4d; d = 3.934_001;

float f = 3_000.5F; f = 5.4f;

decimal myMoney = 3_000.5m; myMoney = 400.75M;

double d = 0.42e2; Console.WriteLine(d); // output 42

float f = 134.45E-2f; Console.WriteLine(f); // output: 1.3445

decimal m = 1.5E6m; Console.WriteLine(m); // output: 1500000

  1. <a name="CgSkN"></a>
  2. ### bool
  3. 只能为true或false
  4. <a name="gvt9V"></a>
  5. ### char
  6. | char | U+0000 到 U+FFFF | 16 位 | [System.Char](https://docs.microsoft.com/zh-CN/dotnet/api/system.char) |
  7. | --- | --- | --- | --- |
  8. char 类型的默认值为 \0,即 U+0000。<br />char 类型支持比较、相等、增量和减量运算符。 此外,对于 char 操作数,算数和逻辑位运算符对相应的字符代码执行操作,并得出 int 类型的结果。<br />可以使用以下命令指定 char 值:
  9. - 字符文本。
  10. - Unicode 转义序列,它是 \u 后跟字符代码的十六进制表示形式(四个符号)。
  11. - 十六进制转义序列,它是 \x 后跟字符代码的十六进制表示形式。
  12. ```csharp
  13. var chars = new[]
  14. {
  15. 'j',
  16. '\u006A',
  17. '\x006A',
  18. (char)106,
  19. };
  20. Console.WriteLine(string.Join(" ", chars)); // output: j j j j

可为null值类型

可为 null 值类型T? 表示其基础值类型 T 的所有值及额外的 null 值。
不能将 null 分配给值类型的变量,除非它是可为 null 的值类型。

内置引用类型:string

string 类型表示零个或多个 Unicode 字符的序列。可以进行拼接、比较等操作。
原始字符串可以使用””” “”” 或 @符号。

3.数据类型的转换

C++

隐式类型转换

当表达式包含不同内置类型的操作数且不存在显式强制转换时,编译器将使用内置标准转换来转换其中一个操作数,以便类型匹配。

扩大转换(提升)

在扩大转换中,较小的变量中的值将赋给较大的变量,同时不会丢失数据。 由于扩大转换始终是安全的,因此编译器会以无提示方式执行它们,并且不会发出警告。 以下转换是扩大转换。

From 功能
signed 或 以外的任何 unsigned 或整 long long 型类型 __int64 double
boolchar 任何其他内置类型
shortwchar_t int, long, long long
int, long long long
float double

收缩转换(强制)

编译器隐式执行收缩转换,但会发出有关数据丢失可能的警告。

  1. int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow
  2. wchar_t wch = 'A'; //OK
  3. char c = wch; // warning C4244:'initializing':conversion from 'wchar_t'
  4. // to 'char', possible loss of data
  5. unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from
  6. // 'int' to 'unsigned char'
  7. int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to
  8. // 'int', possible loss of data
  9. int k = 7.7; // warning C4244:'initializing':conversion from 'double' to
  10. // 'int', possible loss of data

有符号到无符号的转换

有符号整型及其对应的无符号整型的大小总是相同,但它们的区别在于为值转换解释位模式的方式。
建议完全避免有符号到无符号转换。

  1. #include <limits>
  2. using namespace std;
  3. unsigned short unsigned_num = numeric_limits<unsigned short>::max();
  4. short signed_num = unsigned_num;
  5. cout << "unsigned val = " << unsigned_num << " signed val = " << signed_num << endl;
  6. // Prints: "unsigned val = 65535 signed val = -1"
  7. // Go the other way.
  8. signed_num = -1;
  9. unsigned_num = signed_num;
  10. cout << "unsigned val = " << unsigned_num << " signed val = " << signed_num << endl;
  11. // Prints: "unsigned val = 65535 signed val = -1"
  12. unsigned int u3 = 0 - 1;
  13. cout << u3 << endl; // prints 4294967295

显式转换(强制转换)

利用强制转换运算,您可以指示编译器将一种类型的值转换为另一种类型。 在某些情况下,如果两种类型完全无关,编译器将引发错误,但在某些情况下,即使操作不是类型安全的,也不会引发错误。
在 C 样式程序中,同一 C 样式强制转换运算符可用于所有类型的强制转换。

  1. (int) x; // old-style cast, old-style syntax
  2. int(x); // old-style cast, functional syntax

确定旧式强制转换的实际作用可能很困难并且容易出错。 当需要进行强制转换时,我们建议使用以下 C++ 强制转换运算符之一。在某些情况下,它们更加类型安全,并且可以更加明确地表达编程目的:
static_cast,用于仅在编译时检查的强制转换。 static_cast 如果编译器检测到你尝试在完全不兼容的类型之间转换,则 返回错误。您还可以使用它在指向基对象的指针和指向派生对象的指针之间强制转换,但编译器无法总是判断出此类转换在运行时是否安全。

  1. double d = 1.58947;
  2. int i = d; // warning C4244 possible loss of data
  3. int j = static_cast<int>(d); // No warning.
  4. string s = static_cast<string>(d); // Error C2440:cannot convert from
  5. // double to std:string
  6. // No error but not necessarily safe.
  7. Base* b = new Base();
  8. Derived* d2 = static_cast<Derived*>(b);

dynamic_cast,用于从指向基对象的指针到指向派生对象的指针的、安全且经过运行时检查的强制转换。 对于 dynamic_cast 向下转换, 比static_cast 更安全,但运行时检查会产生一些开销。

  1. Base* b = new Base();
  2. // Run-time check to determine whether b is actually a Derived*
  3. Derived* d3 = dynamic_cast<Derived*>(b);
  4. // If b was originally a Derived*, then d3 is a valid pointer.
  5. if(d3)
  6. {
  7. // Safe to call Derived method.
  8. cout << d3->DoSomethingMore() << endl;
  9. }
  10. else
  11. {
  12. // Run-time check failed.
  13. cout << "d3 is null" << endl;
  14. }
  15. //Output: d3 is null;

const_cast,用于将const转非const,或非const 变量转换为 const。

  1. void Func(double& d) { ... }
  2. void ConstCast()
  3. {
  4. const double pi = 3.14;
  5. Func(const_cast<double&>(pi)); //No error.
  6. }

其他类型转bool

用整型、浮点型给bool对象赋值,0则为false,非零值为true。
用字符给bool对象赋值,’\0’为false,其他为true。
字符串字面量不能给bool赋值。

  1. -2 == true
  2. 0 == false
  3. 0.1 == true
  4. 0.0 == false
  5. 'a' == true
  6. '0' == true
  7. '\0' == false
  8. '\n' == true
  9. '\r' == true
  10. '\b' == true
  11. true == true
  12. false == false
  13. cout << "\"2\" == " << (st ? T : F) << '\n'; //error: could not convert ‘st’ to ‘bool’
  14. cout << "\"0\" == " << (sf ? T : F) << '\n'; //error: could not convert ‘sf’ to ‘bool’

bool转标准string

  1. bool a = true;
  2. ostringstream os1;
  3. os1 << a;
  4. cout << string(os1.str()) << endl; //1
  5. ostringstream os2;
  6. a = false;
  7. os2 << a;
  8. cout << string(os2.str()) << endl; //0
  9. stringstream ss1;
  10. ss1 << true;
  11. cout << ss1.str() << endl;//1
  12. stringstream ss2;
  13. ss2 << false;
  14. cout << ss2.str() << endl;//0

标准string转bool

  1. bool b;
  2. string s = "true";
  3. istringstream(s) >> boolalpha >> b;
  4. cout << "b = " << b << endl; //b=true
  5. s = "false";
  6. istringstream(s) >> boolalpha >> b;
  7. cout << "b = " << b << endl; //b=false

string和整型/浮点型的相互转换

头文件中定义了容器类模板 basic_string 。string类包含了类型转换函数:

名称 说明
stod 将字符序列转换为 double
stof 将字符序列转换为 float
stoi 将字符序列转换为 int
stold 将字符序列转换为 long double
stoll 将字符序列转换为 long long
stoul 将字符序列转换为 unsigned long
stoull 将字符序列转换为 unsigned long long
to_string 将一个值转换为 string。
to_wstring 将一个值转换为宽字符串。

以stoi函数为例:该函数将 str 中的字符序列转换为类型 int 值并返回该值。 例如,在传递字符序列“10”时,stoi 返回的值为整数 10。
to_string函数可以将一个整型值或浮点值转为string。

  1. string to_string(int value);
  2. string to_string(unsigned int value);
  3. string to_string(long value);
  4. string to_string(unsigned long value);
  5. string to_string(long long value);
  6. string to_string(unsigned long long value);
  7. string to_string(float value);
  8. string to_string(double value);
  9. string to_string(long double value);

string库与c样式字符串的转换

由类型 basic_string 对象控制的序列是标准 C++ 字符串类,通常称为string,但它们不应与在整个 C++ 标准库中使用的以 null 结尾的 C 样式字符串混淆。 标准 C++ 字符串是一个容器,可用于将字符串用作常规类型,例如比较和串联操作、迭代器、C++ 标准库算法,以及使用类分配器托管内存进行复制和分配。 如果需要将标准 C++ 字符串转换为以 null 结尾的 C 样式字符串,请使用 basic_string::c_str 该成员。
标准C++ string类有很多成员函数,可以对容器中的字符进行操作:

成员函数 说明
append 向字符串的末尾添加字符。
assign 对字符串的内容赋新的字符值。
at 返回对字符串中指定位置的元素的引用。
back
begin 返回发现字符串中第一个元素的位置的迭代器。
data 将字符串的内容转换为字符数组。
c_str 将字符串的内容转换为以 null 结尾的 C 样式字符串。
find 向前搜索字符串,搜索与指定字符序列匹配的第一个子字符串。
length 返回字符串中元素的当前数目。
size 返回字符串中元素的当前数目。
replace 用指定字符或者从其他范围、字符串或 C 字符串复制的字符来替代字符串中指定位置的元素。
substr 从字符串起始处的指定位置复制最多某个数目的字符的子字符串。
starts_withC++20 检查字符串是否以指定的前缀开头。
operator+= 向字符串追加字符。
operator= 对字符串的内容赋新的字符值。
operator[] 使用字符串中的指定索引提供对字符的引用。
  1. #include <string>
  2. #include <iostream>
  3. int main( )
  4. {
  5. using namespace std;
  6. string str1 ( "Hello world" );
  7. cout << "The original string object str1 is: "<< str1 << endl;
  8. cout << "The length of the string object str1 = "<< str1.length ( ) << endl << endl;
  9. // Converting a string to an array of characters
  10. const char *ptr1 = 0;
  11. ptr1= str1.data ( );
  12. cout << "The modified string object ptr1 is: " << ptr1<< endl;
  13. cout << "The length of character array str1 = "<< strlen ( ptr1) << endl << endl;
  14. // Converting a string to a C-style string
  15. const char *c_str1 = str1.c_str ( );
  16. cout << "The C-style string c_str1 is: " << c_str1<< endl;
  17. cout << "The length of C-style string str1 = "<< strlen ( c_str1) << endl << endl;
  18. }
  1. The original string object str1 is: Hello world
  2. The length of the string object str1 = 11
  3. The modified string object ptr1 is: Hello world
  4. The length of character array str1 = 11
  5. The C-style string c_str1 is: Hello world
  6. The length of C-style string str1 = 11

string和char的运算

对char类型调用to_string函数,是将其ASCII数字转换为string。

  1. cout<<to_string('a')<<endl; //97

标准string与char相加,会将char追接到string中。

  1. string str = "ab";
  2. cout<<str+'c'<<endl; //abc

也可以使用append函数来追加n个字符。

  1. string str = "ab";
  2. str.append(1,'c');
  3. cout<<str<<endl;

字符串的字面量默认是char数组,所以不可以直接进行相加,也无法使用标准string相关API。

  1. cout<<"ab"+"cd"<<endl;//error

C

整型类型转换

可以将任何整型数值类型转换为其他整数数值类型。 如果目标类型可以存储源类型的所有值,则转换是隐式的。 否则,需要使用强制转换表达式来执行显式转换。

浮点型转换

浮点数值类型之间只有一种隐式转换:从 float 到 double。 但是,可以使用显式强制转换将任何浮点类型转换为任何其他浮点类型。

布尔型转换

整型不能转为bool。

字符型转换

char 类型可隐式转换为以下整型类型:ushort、int、uint、long 和 ulong。 它也可以隐式转换为内置浮点数值类型:float、double 和 decimal。 它可以显式转换为 sbyte、byte 和 short 整型类型。
无法将其他类型隐式转换为 char 类型。 但是,任何整型或浮点数值类型都可显式转换为 char。