当前位置:   article > 正文

【C/C++ 基本数据类型】C++ 基本数据类型深度解析与C语言对比

【C/C++ 基本数据类型】C++ 基本数据类型深度解析与C语言对比


1. 引言

1.1 C++与C语言的关系

C++并非在空中凭空诞生,它深深地扎根于其前身:C语言。C++是由Bjarne Stroustrup在Bell实验室开发的,最初被命名为"C with Classes"(带有类的C语言),后来被重新命名为C++。"++"是C语言中的增量运算符,象征着C++是C语言的一个进步。C++在语法和设计上保留了很多C语言的元素,同时也引入了对象导向编程的概念,如类(class)和对象(object),并且在C++11以后的版本中,引入了一些现代编程语言的特性,比如智能指针(smart pointers),lambda表达式等。

在理解C++的基本数据类型时,我们通常会发现,C++与C语言在数据类型上有许多相似之处。然而,C++还引入了一些新的数据类型,以支持其更复杂的编程范式。因此,理解这些C++特有的数据类型,以及它们与C语言数据类型的区别,将使我们能更好地理解和使用C++。

1.2 对本文的简要概述

本文将深入探讨C++的基本数据类型,并在适当的地方对其与C语言进行对比。我们将从整型、浮点型、字符型、布尔型、枚举型和void类型开始,然后探讨复合类型,如数组、结构体、共用体、类、指针和引用。在理解了这些基本类型之后,我们将深入探讨C++11、C++14、C++17、C++20在数据类型上的改进和扩展。

内容丰富且涵盖广泛可能会让人觉得有些难以消化,但记住,金针在磨石上磨砺才能更加锋利。就像Friedrich Nietzsche的名言:“That which does not kill us makes us stronger”(那些未能杀死我们的东西,会让我们变得更强),通过深入理解和实践,我们可以更好地掌握C++的数据类型,进一步提升我们的编程能力。

在本文的最后,我们还将提供一些参考资料,以供读者进一步学习和参考。现在,让我们开始我们的旅程,深入探索C++的世界。

在前进的路上,每一步都是必要的,每一步都让我们更接近终点。就像著名的C++专家Scott Meyers在他的《Effective C++》一书中强调,要成为一个成功的C++程序员,不仅要理解C++的语法,更要理解C++的语义和哲学。让我们以开放的心态,接受新的知识,迈出掌握C++数据类型的第一步吧!

2. C++基本数据类型

2.1 整型

整型(integer)是我们在编程中最常见的数据类型之一。在C++中,整型可以有多种类型,其中包括 shortintlonglong long。这些类型的大小可能根据不同的编译器和操作系统有所不同,但通常 short 是最小的,而 long long 是最大的。

接下来,让我们更详细地了解这些整型。

2.1.1 short, int, long, long long

这些类型的主要区别在于它们的大小和值的范围。下表比较了这些类型的典型大小和值范围:

类型大小(字节)范围
short2-32,768 到 32,767
int4-2,147,483,648 到 2,147,483,647
long4-2,147,483,648 到 2,147,483,647
long long8-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807

这些值的范围是基于二进制补码表示法计算的。在实际编程中,我们可以通过 sizeof 运算符来确定特定系统中这些类型的具体大小。

#include <iostream>

int main() {
    std::cout << "Size of short: " << sizeof(short) << " bytes" << std::endl;
    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    std::cout << "Size of long: " << sizeof(long) << " bytes" << std::endl;
    std::cout << "Size of long long: " << sizeof(long long) << " bytes" << std::endl;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.1.2 无符号与有符号整型

除了上述的基本整型,C++还提供了无符号(unsigned)版本的整型,包括 unsigned shortunsigned intunsigned longunsigned long long。无符号整型只能表示非负整数,这意味着它们的范围从0开始。

unsigned int a = 10; // a is a non-negative integer
  • 1

2.1.3 字面量

在C++中,我们可以通过字面量(literal)直接在代码中表示特定的值。对于整型,我们可以使用十进制、八进制、十六进制和二进制字面量。

int dec_val = 42;          // decimal literal (base 10)
int oct_val = 052;         // octal literal (base 8)
int hex_val = 0x2a;        // hexadecimal literal (base 16)
int bin_val = 0b101010;    // binary literal (base 2, since C++14)
  • 1
  • 2
  • 3
  • 4

这里,052是八进制字面量,0x2a是十六进制字面量,0b101010是二进制字面量。值得注意的是,二进制字面量在C++14及以后的版本中才被引入。

2.2 浮点型

浮点型(floating-point)是用来表示实数的,包括小数和大的科学记数。在C++中,浮点型有三种:floatdoublelong double

2.2.1 float, double

floatdouble 是两种基本的浮点类型。double 的精度通常比 float 高,也就是说,double 能够表示更精确的数值。

float f = 3.14f;      // f or F suffix for float
double d = 3.14;      // no suffix for double
  • 1
  • 2

long double 则提供了比 double 更高的精度,但其大小和精度可能因编译器和平台的不同而不同。

long double ld = 3.14l; // l or L suffix for long double
  • 1

2.2.2 精度和范围

浮点数的精度和范围取决于其大小,但具体的值可能因编译器和平台的不同而不同。下表给出了这些类型的典型大小和精度:

类型大小(字节)精度(有效数字位数)近似范围
float46-71.2E-38 to 3.4E+38
double815-162.3E-308 to 1.7E+308
long double12-1619-203.4E-4932 to 1.1E+4932 (on most common platforms)

2.2.3 字面量

浮点数的字面量可以由整数部分、小数部分和/或指数部分组成。指数是通过 ‘e’ 或 ‘E’ 表示的,表示10的幂。

double d1 = 3.14;       // 3.14
double d2 = 6.02e23;    // 6.02 x 10^23
double d3 = 1.6e-19;    // 1.6 x 10^-19
double d4 = 0.1e1;      // 0.1 x 10^1 = 1.0
  • 1
  • 2
  • 3
  • 4

2.3 字符型

字符型(char)用于表示单个字符。在C++中,它通常用来表示ASCII字符。但是,C++还提供了几种其他的字符类型:wchar_tchar16_tchar32_t,它们用于表示宽字符或Unicode字符。

2.3.1 char, wchar_t, char16_t, char32_t

char 是最基本的字符类型,通常用于表示ASCII字符。

char c = 'a';
  • 1

wchar_tchar16_tchar32_t 用于表示宽字符或Unicode字符。

wchar_t wc = L'あ';       // wide character
char16_t c16 = u'あ';     // Unicode character (since C++11)
char32_t c32 = U'あ';     // Unicode character (since C++11)
  • 1
  • 2
  • 3

注意,字符字面量前的 LuU 前缀用于表示宽字符和Unicode字符。

2.3.2 字面量

字符的字面量是通过单引号表示的。

char c1 = 'a';           // character```cpp
wchar_t c2 = L'あ';      // wide character
char16_t c3 = u'あ';     // Unicode character (since C++11)
char32_t c4 = U'あ';     // Unicode character (since C++11)
  • 1
  • 2
  • 3
  • 4

我们也可以用字符字面值来表示特殊的字符,比如换行符 (\n)、制表符 (\t) 等。

char newline = '\n';
char tab = '\t';
  • 1
  • 2

2.4 布尔型

布尔型(boolean)是一种特殊的数据类型,只有两个值:truefalse。在C++中,布尔类型被称为 bool

bool b1 = true;
bool b2 = false;
  • 1
  • 2

布尔型常用于条件判断和循环控制。

if (b1) {
    // This block will execute because b1 is true
}

while (!b2) {
    // This loop will continue because b2 is false
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.5 枚举型

枚举类型(enumeration)是一种用户定义的类型,它包括一组命名的值。在C++中,我们可以使用 enum 关键字创建枚举类型。

enum Color {
    RED,    // 0 by default
    GREEN,  // 1
    BLUE    // 2
};
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以创建枚举类型的变量,并使用枚举值来初始化它。

Color myColor = BLUE;
  • 1

从C++11开始,我们还可以使用强类型枚举(也称为枚举类,enum class)。

enum class Fruit {
    APPLE,
    BANANA,
    CHERRY
};

Fruit myFruit = Fruit::CHERRY;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

枚举类提供了更好的类型安全,因为它们的枚举值不会隐式转换为整数,也不能与其他枚举类型的值进行比较。

2.6 Void类型

在C++中,void 是一种特殊的类型,它表示"无类型"。void 主要在三个地方使用:函数的返回类型、函数的参数以及指针的类型。

  • 当函数不需要返回任何值时,我们可以使用 void 作为函数的返回类型。
void printHello() {
    std::cout << "Hello, world!" << std::endl;
}
  • 1
  • 2
  • 3
  • 当函数不需要任何参数时,我们可以使用 void 作为函数的参数。
void doNothing(void) {
    // This function does nothing.
}
  • 1
  • 2
  • 3
  • 我们可以声明一个类型为 void 的指针,它可以指向任何类型的对象。但是,我们不能直接通过 void 指针访问对象,必须先将 void 指针转换为具体类型的指针。
int i = 42;
void *ptr = &i;
int *int_ptr = static_cast<int*>(ptr);
std::cout << *int_ptr << std::endl;  // Outputs: 42
  • 1
  • 2
  • 3
  • 4

2.7 复合类型

除了基本数据类型,C++还提供了一些复合类型(compound types),包括数组、结构体、共用体、类、指针和引用。

2.7.1 数组

数组是由相同类型的元素组成的集合,其中所有元素在内存中连续存储。数组的大小在声明时必须确定,且在其生命周期内不能改变。

int nums[5] = {1, 2, 3, 4, 5};
  • 1

2.7.2 结构体

结构体(struct)是一种用户定义的类型,可以容纳不同类型的数据。结构体在C++中主要用于组织和存储相关联的数据。

struct Student {
    std::string name;
    int age;
};

Student s = {"Alice", 20};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.7.3 共用体

共用体(union)与结构体类似,但在任何时候只能存储一个成员的值。换句话说,一个联合可以有多种类型,但只能代表一种类型。

union Mix {
    int i;
    double d;
};

Mix m;
m.d = 3.14;  // Now the union holds a double value
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.7.4 类

类(class)是C++中最重要的特性之一,它代表了面向对象编程的核心。类是用户定义的类型,它定义了一组数据和对数据的操作。

class Circle {
public:
    Circle(double r) : radius(r) {}

    double area() {
        return 3.14 * radius * radius;
    }

private:
    double radius;
};

Circle c(1.0);
std::cout << c.area() << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.7.5 指针和引用

指针(pointer)和引用(reference)是C++中重要的复合类型。指针是存储另一种类型对象的内存地址的对象,而引用则是另一种类型对象的别名。

int i = 42;
int *p = &i;   // p is a pointer to i
int &r = i;    // r is a reference to i
  • 1
  • 2
  • 3

以上就是C++的基本数据类型以及复合类型的介绍,了解了这些,我们就可以更好地理解C++程序的工作原理,也可以编写出更高效、更安全的## 2.7.6 函数指针

函数指针(Function Pointer)是指向函数的指针变量。和其他指针变量一样,函数指针指向的是存有特定值的内存地址。这个特定的值是什么呢?是函数在内存中的位置。函数指针可以用来调用函数,并提供函数作为其他函数的参数。函数指针对于实现高阶函数和回调函数(callback function)非常有用。

// 函数原型
void fun(int a, int b) {
    std::cout << "a + b = " << a + b << std::endl;
}

int main() {
    // 定义函数指针 p
    void (*p)(int, int);
    // 让 p 指向 fun 函数
    p = fun;
    // 通过指针调用函数
    (*p)(2, 3);  // 输出:a + b = 5
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在上述例子中,(*p)(2, 3);fun(2, 3); 是等价的。这是因为 p 是指向 fun 函数的指针,通过 (*p) 我们得到的就是 fun 函数,所以 (*p)(2, 3); 就等价于 fun(2, 3);

2.7.7 函数引用

C++也支持函数引用,它是函数的别名,可以用来直接调用函数。和函数指针一样,函数引用也可以用来实现回调函数和高阶函数。

// 函数原型
void fun(int a, int b) {
    std::cout << "a + b = " << a + b << std::endl;
}

// 函数类型别名
using Fun = void(int, int);

int main() {
    // 定义函数引用
    Fun& f = fun;
    // 通过引用调用函数
    f(2, 3);  // 输出:a + b = 5
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在上述例子中,f(2, 3);fun(2, 3); 是等价的。这是因为 f 是 fun 函数的引用,所以 f(2, 3); 就等价于 fun(2, 3);

2.7.8 std::array和std::vector

除了上述的基本数据类型和复合类型,C++标准库还提供了一些其他的数据类型,如 std::arraystd::vector

std::array 是一个固定大小的数组,它的大小在编译时需要确定。

std::array<int, 5> arr = {1, 2, 3, 4, 5};
  • 1

std::vector 是一种动态数组,它的大小可以在运行时改变。

std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);  // Now the size of vec is 6
  • 1
  • 2

std::arraystd::vector 提供了许多有用的方法,如 size()at()front()back() 等,使得我们可以更方便地操作数组。

以上就是C++的基本数据类型以及复合类型的介绍,了解了这些,我们就可以更好地理解C++程序的工作原理,也可以编写出更高效、更安全的代码。

3. C++与C语言数据类型的对比

虽然C++和C语言在很大程度上是相似的,但在数据类型方面,他们也存在着一些重要的差异。本节将探讨这些差异,以帮助您更好地理解这两种语言之间的联系与区别。

3.1 数据类型的差异

  • 布尔类型:C++有一个内建的布尔类型bool,其值可以是truefalse。虽然C语言中没有内建的布尔类型,但它通常使用int来表示布尔值,0表示false,非零值表示true

  • 字符串类型:在C++中,字符串可以被表示为std::string对象。std::string类提供了许多方便的方法,如获取字符串的长度、连接字符串等。而在C语言中,字符串通常表示为字符数组。

  • 复合类型:C++提供了类(class)和引用(reference),这是C语言中没有的。类是面向对象编程的基础,而引用提供了对另一个变量的别名,可以作为函数参数,提高代码的效率和易读性。

  • 新的数据类型:C++11及其后续版本引入了一些新的数据类型,如auto用于自动类型推断,nullptr用于空指针,enum class用于强类型枚举等。

3.2 命名约定的差异

在C语言中,所有的类型名(包括结构体和联合体)都位于同一命名空间中。而在C++中,结构体和联合体有自己的命名空间,这意味着我们可以在不同的结构体中使用相同的成员名,而不会发生冲突。

3.3 使用场景的差异

由于C++支持面向对象编程,所以在设计数据结构和算法时,我们往往会使用类和对象。而在C语言中,我们通常会使用结构体和函数。

此外,C++中的数据类型通常更加灵活和强大。例如,std::vector可以动态地调整大小,std::string可以方便地处理字符串,std::mapstd::set可以方便地处理键值对和集合等。

3.4 初始化的差异

  • 统一初始化:C++11引入了统一的初始化语法,使用花括号{}进行初始化。这种初始化方式可以用于任何数据类型,包括数组、结构体、类和其他复合类型。例如,我们可以使用int a{5};来初始化一个整数,或者使用std::vector<int> v{1, 2, 3};来初始化一个向量。这种初始化方式提供了更强的类型检查,可以避免某些类型的隐式转换。

  • 列表初始化:在C++中,我们可以使用列表初始化来初始化数组或容器。例如,int arr[] = {1, 2, 3};std::vector<int> vec = {4, 5, 6};。而在C语言中,只能使用列表初始化来初始化数组。

  • 构造函数初始化:C++中的类可以有构造函数,这允许我们在创建对象时进行初始化。例如,std::string str("Hello");。C语言没有类和构造函数的概念,所以不能使用这种方式进行初始化。

  • 默认初始化:在C++中,局部变量默认不进行初始化,这可能导致未定义的行为。但是,类的成员变量和全局变量会被自动初始化为默认值。而在C语言中,只有全局变量和静态变量会被自动初始化为默认值。

3.5 类型推断的差异

  • auto关键字:C++11引入了auto关键字,允许编译器自动推断变量的类型。这在处理复杂的数据类型,如迭代器或lambda表达式时非常有用。例如,auto it = vec.begin();。C语言中没有这种类型推断的功能。

3.6 类型别名的差异

  • typedefusing:在C语言中,我们使用typedef来为类型创建别名。而在C++中,除了typedef之外,还可以使用using关键字来创建类型别名,这在模板编程中尤为有用。例如,using VecInt = std::vector<int>;

3.7 整数类型初始化的差异

  • 函数风格的类型转换:在C++中,我们可以使用函数风格的类型转换来初始化或转换数据类型。例如,int a = int(3.14);会将浮点数3.14转换为整数3。这种转换方式在C++中是有效的,但在C语言中是不支持的。

  • 静态类型转换:除了函数风格的类型转换,C++还提供了static_cast来进行类型转换。例如,int b = static_cast<int>(3.14);。这种转换方式提供了更明确的类型转换意图,并且在某些情况下比C语言的强制类型转换更安全。

这两种初始化和转换方式在C++中都是常见的,但在C语言中是不可用的。这也是C++和C语言在数据类型处理上的另一个重要差异。

4. C++11、C++14、C++17、C++20在数据类型上的改进和扩展

从C++11开始,C++在每个新的版本中都引入了许多新的数据类型和特性。这些新的特性在很大程度上提高了C++的表达能力和效率,使得编程更加方便和强大。

4.1 自动类型推断(auto)

C++11引入了auto关键字,用于自动类型推断。这意味着,编译器可以根据初始化表达式的类型自动推断变量的类型。

auto i = 42;           // i has type int
auto d = 3.14;         // d has type double
auto s = "hello";      // s has type const char*
auto v = std::vector<int>{1, 2, 3};  // v has type std::vector<int>
  • 1
  • 2
  • 3
  • 4

auto关键字可以大大减少代码的冗余,并提高编程效率。例如,当我们需要声明一个复杂类型的变量时,auto可以帮助我们简化代码。

std::map<std::string, std::vector<int>>::iterator it = m.begin();  // without auto
auto it = m.begin();  // with auto
  • 1
  • 2

4.2 基于范围的for循环

C++11引入了基于范围的for循环(range-based for loop),这使得对容器(如数组和std::vector)的遍历变得更加简单和清晰。

std::vector<int> v = {1, 2, 3, 4, 5};
for (int i : v) {
    std::cout << i << std::endl;
}
  • 1
  • 2
  • 3
  • 4

在这个例子中,i : v表示"在v中的每个元素i"。这种语法使得代码更加易读和直观。

4.3 nullptr和强类型枚举

在C++11中,引入了nullptr关键字,用于表示空指针。在之前的版本中,我们通常使用NULL0来表示空指针,但这会导致一些问题,因为NULL0也可以表示整数。nullptr是一种特殊类型的字面量,它只能转换为指针类型,这提高了代码的安全性和清晰性。

int* p1 = nullptr;  // OK
int i = nullptr;    // Error: nullptr cannot convert to int
  • 1
  • 2

C++11还引入了强类型枚举(enum class)。在之前的版本中,枚举类型的值可以隐式转换为整数,这会导致一些问题。强类型枚举的值不能隐式转换为其他类型,这提高了代码的安全性。

enum class Color {RED, GREEN, BLUE};
Color c = Color::RED;
int i = c;  // Error: Color::RED cannot convert to int
  • 1
  • 2
  • 3

4.4 可选类型(optional)

C++17引入了std::optional,这是一种可以存储值或不存储值的容器。当我们的函数可能不返回值时(例如,查找操作可能找不到元素),我们可以使用std::optional来表示这种情况。

std::optional<int> find(const std::vector<int>& v, int x) {
    for (int i : v) {
        if (i == x) return i;
    }
    return std::nullopt;  // or return {};
}

auto result = find(v, 42);
if (result.has_value()) {
    std::cout << "Found: " <<*result << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这个例子中,如果找到了元素,find函数会返回该元素;否则,它会返回一个不含有值的std::optional。我们可以使用has_value成员函数来检查std::optional是否含有值。

4.5 任意类型(any)

C++17还引入了std::any类型,它可以存储任意类型的值,类似于C#的object类型或Java的Object类型。我们可以使用std::any_cast来获取存储在std::any中的值。

std::any a = 42;
try {
    std::cout << std::any_cast<int>(a) << std::endl;
} catch (const std::bad_any_cast& e) {
    std::cout << "Bad any cast: " << e.what() << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这个例子中,如果std::any_cast不能将std::any转换为指定的类型,它会抛出一个std::bad_any_cast异常。

4.6 变体类型(variant)

C++17还引入了std::variant类型,它可以存储多种但数量有限的类型的值。std::variant是一个类型安全的联合体,我们可以使用std::getstd::get_if来获取存储在std::variant中的值。

std::variant<int, double, std::string> v;
v = 3.14;
try {
    std::cout << std::get<double>(v) << std::endl;
} catch (const std::bad_variant_access& e) {
    std::cout << "Bad variant access: " << e.what() << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这个例子中,如果std::get不能将std::variant转换为指定的类型,它会抛出一个std::bad_variant_access异常。

以上就是C++11、C++14、C++17和C++20在数据类型上的一些主要改进和扩展。这些新的特性在很大程度上提高了C++的表达能力和效率,使得编程更加方便和强大。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。


阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/545970
推荐阅读
相关标签
  

闽ICP备14008679号