在C++编程中,了解各类数据类型也是至关重要的。下面我会总结一下C++中的数据类型,包括基本类型,符合类型和自定义类型。方便自己整理和理解。
1,基本类型
C++中的基本类型是构建其他数据类型的基础,常见的基础类型包括整型,浮点型,字符型和布尔型:
- 整型:用于表示整数,如
int
、short
、long
等。 - 浮点型:用于表示带小数部分的数值,如
float
、double
等。 - 字符型:用于表示单个字符,如
char
。 - 布尔型:用于表示逻辑值,只能取
true
或false
。
每种数据类型在不同平台上的大小可能会有所不同,但通常遵循一定的规则,具体分析如下:
整型:
int
:通常占用 4 个字节(32 位),范围为 -2147483648 到 2147483647。short
:通常占用 2 个字节(16 位),范围为 -32768 到 32767。long
:通常占用 4 个字节(32 位),范围与int
类似,但取决于平台,有时会更大。long long
:通常占用 8 个字节(64 位),范围为 -9223372036854775808 到 9223372036854775807。
浮点型:
float
:通常占用 4 个字节(32 位),范围为约 ±3.4e-38 到 ±3.4e38,精度为约 6-7 位小数。double
:通常占用 8 个字节(64 位),范围为约 ±1.7e-308 到 ±1.7e308,精度为约 15-16 位小数。long double
:通常占用 12 或 16 个字节,范围和精度比double
更大,具体取决于编译器和平台。
字符型:
char
:通常占用 1 个字节,表示一个字符(ASCII 码或其他字符集中的字符)。
char
类型可以是有符号的(signed char)或无符号的(unsigned char)。默认情况下,char
类型被视为有符号的,但可以使用 unsigned char
显式地声明无符号的字符。
有符号字符(signed char):有符号字符可以表示正数、负数和零。对于有符号字符,大多数编译器使用补码表示法来表示负数。范围为 -128 到 127(包括)。
无符号字符(unsigned char):无符号字符只能表示非负数(即正数和零),范围为 0 到 255(包括)。无符号字符通常用于处理字节数据,因为它们可以表示更大的范围。
示例代码:
#include <iostream>
int main() {
char c1 = 'A'; // 有符号字符
unsigned char c2 = 'B'; // 无符号字符
std::cout << "Signed char: " << c1 << std::endl;
std::cout << "Unsigned char: " << c2 << std::endl;
return 0;
}
在上面的示例中,c1
和 c2
都存储了一个字符。c1
是一个有符号字符,c2
是一个无符号字符。由于 char
类型通常是有符号的,所以在使用 unsigned char
时需要格外注意范围,避免发生意外的结果。
布尔型:
bool
:通常占用 1 个字节,值为true
或false
。
打印基本字符示例代码:
#include <iostream>
int main() {
int num = 10;
float pi = 3.14;
char letter = 'A';
bool isTrue = true;
std::cout << "Integer: " << num << std::endl;
std::cout << "Float: " << pi << std::endl;
std::cout << "Character: " << letter << std::endl;
std::cout << "Boolean: " << isTrue << std::endl;
return 0;
}
要查看特定数据类型在当前平台的大小,可以使用`sizeof` 运算符。例如,要查看`int`的大小,可以编写以下代码:
#include <iostream>
int main() {
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
return 0;
}
编译并运行这段代码,将输出 `Size of int: 4 bytes`,表示 `int` 类型在当前平台上占用四个字节。
2,复合类型
C++中的复合数据类型是由基本类型或其他复合数据类型组成的类型,包括数组,指针,引用和结构体。每种复合数据类型在内存中的存储方式和大小都有所不同,以下是C++中常见的复合数据类型及其特点:
数组:
- 数组是由相同类型的元素组成的集合。
- 数据的大小在创建时就确定,并且在整个生命周期中保持不变
- 访问数组元素时,可以使用下标(索引)来访问特定位置的元素。
指针:
- 指针是存储其他变量内存地址的变量。
- 指针可以指向任何数据类型的变量,甚至可以指向函数。
- 通过指针,可以直接访问或修改指向变量的值。
引用:
- 引用为变量取一个别名,用于简化代码和提高效率。
- 引用在创建时,必须初始化,并且一旦引用被初始化后,就不能再引用其他变量。
结构体:
- 结构体是用户自定义的数据类型,用于将不同类型的数据组合在一起形成一个新的数据类型。
- 结构体的成员可以包括基本数据类型,复合数据类型甚至其他结构体。
示例代码:
#include <iostream>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
int& ref = num;
std::cout << "Array: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
std::cout << "Pointer: " << *ptr << std::endl;
std::cout << "Reference: " << ref << std::endl;
return 0;
}
具体的使用示例:
// 数组
int arr[5]; // 声明一个包含5个整数的数组
// 指针
int num = 10;
int* ptr = # // 声明一个指向整数的指针,并将其指向num的地址
// 引用
int num = 10;
int& ref = num; // 声明一个引用ref,其别名为num
// 结构体
struct Person {
std::string name;
int age;
};
Person p1 = {"Alice", 30}; // 声明一个Person类型的结构体变量p1
2.1 引用和指针的联系和区别
引用(Reference)和 指针(Pointer) 是C++中常用的两种机制,用于间接访问变量。总的来说,引用是一种更安全,更直观的机制,用于简化代码和提高效率;而指针则更加灵活,可以在运行时动态地指向不同的对象,在选择使用引用还是指针时,应根据具体的需求和情况来决定。
他们有以下联系:
-
都可以用于函数参数传递:
- 引用和指针都可以用于函数参数传递,可以实现对参数的修改。
-
都可以用于返回引用或指针:
- 函数可以返回引用或指针,让调用者可以访问函数内部的变量。
-
都可以用于动态内存管理:
- 指针通常用于动态内存分配和释放(
new
和delete
)。 - 引用通常用于简化代码和提高效率,但不能指向动态分配的内存。
- 指针通常用于动态内存分配和释放(
-
都可以用于迭代器:
- 在 STL 中,迭代器通常是指针或类似指针的对象,用于遍历容器中的元素。
- 引用也可以用于类似的目的,但较少见。
他们有几个主要的区别:
声明和初始化:
- 引用使用`&`符号进行声明和初始化,引用在创建时必须初始化,并且一旦初始化后,不能再引用其他变量。
- 指针使用 `*` 符号进行声明和初始化,指针可以在任何时候被赋值为另一个地址。
int num = 10;
int& ref = num; // 引用
int* ptr = # // 指针
空值:
- 引用不允许为空,必须在初始化时绑定到一个合法的对象。
- 指针可以为空,可以指向空(nullptr)或未初始化的内存地址。
操作:
- 引用在使用时不需要解引用操作符 `*`,因为他本身就是目标变量的别名。
- 指针需要解引用操作符 `*`来访问目标变量的值。
int num = 10;
int& ref = num; // 引用
int* ptr = &num // 指针
int val1 = ref; // 直接访问 num 的值
int val2 = *ptr; // 使用解引用操作符访问 num 的值
操作的对象:
- 引用只能和变量绑定,不能指向其他引用或无法取地址的临时对象。引用没有自己的地址,因此不能对引用进行取地址操作(&ref 是错误的)。
- 指针可以指向其他指针,或无法取地址的临时对象。
3,自定义类型
在C++中,可以使用class和struct 关键字来创建自定义类型,这些类型可以包含自定义的数据成员和成员函数。自定义数据类型的理解可以简单地归纳为以下几点:
-
抽象数据类型:自定义数据类型可以将多个数据组合在一起,形成一个新的抽象数据类型,用于描述某种概念或实体。
-
模块化:自定义数据类型可以帮助我们将程序模块化,将相关的数据和操作封装在一起,提高代码的可读性和可维护性。
-
代码复用:通过定义自定义数据类型,可以在程序中多次使用相同的数据结构,从而提高代码的复用性。
-
数据封装:自定义数据类型可以通过访问控制符(如
public
、private
、protected
)实现数据封装,隐藏数据的具体实现细节,提高数据的安全性和可靠性。
总的来说,自定义数据类型是 C++ 中非常重要的概念,它能够帮助我们更好地组织和管理数据,提高程序的可读性、可维护性和复用性。
下面是一个class的示例代码:
#include <iostream>
class Point {
private:
int x;
int y;
public:
Point(int xCoord, int yCoord) : x(xCoord), y(yCoord) {}
void display() {
std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Point p(3, 4);
p.display();
return 0;
}
下面是struct的示例代码:
#include <iostream>
using namespace std;
// 定义一个结构体表示学生
struct Student {
string name;
int age;
float score;
};
int main() {
// 声明一个结构体变量
Student stu;
// 对结构体变量赋值
stu.name = "Alice";
stu.age = 20;
stu.score = 90.5;
// 输出结构体变量的值
cout << "Name: " << stu.name << endl;
cout << "Age: " << stu.age << endl;
cout << "Score: " << stu.score << endl;
return 0;
}
在上面的例子中,Student
是一个自定义的数据类型,它由三个成员组成:name
、age
和 score
。我们可以使用 Student
类型来声明变量 stu
,并对其进行操作,就像操作内置数据类型一样。这样,我们就可以更灵活地组织和管理数据。
4,特殊的数据类型------枚举
枚举数据类型是 C++ 中的一种特殊类型,它既不属于基本数据类型,也不属于复合数据类型,而是一种独立的数据类型。枚举类型用于定义一组具有相关性的常量,这些常量被称为枚举值。枚举值可以像整数一样使用,但其取值被限定为预先定义的枚举列表中的一个。
在 C++ 中,枚举类型使用 enum
关键字进行定义。例如:
enum Color {
RED,
GREEN,
BLUE
};
int main() {
Color c = RED;
return 0;
}
在这个例子中,Color
是一个枚举类型,它定义了三个枚举值 RED
、GREEN
和 BLUE
。在声明枚举类型变量时,可以将其赋值为枚举值中的一个。
虽然枚举类型可以被看作是一种自定义数据类型,但由于其特殊的定义和用法,通常不将其归类为基本数据类型或复合数据类型。它更像是一种特殊的常量集合,用于提高代码的可读性和可维护性。
当你在编写程序时,可能会遇到一些情况,需要用到一组固定的常量。例如,你可能想表示一周中的每一天,或者表示一种颜色。为了更好地组织这些常量并使代码更易读,C++ 提供了枚举(enumeration)类型。
4.1 什么是枚举?
枚举是一种用户定义的数据类型,用于定义一组命名的整数常量。这些常量称为枚举值。枚举类型提供了一种将常量组织在一起并给它们赋予有意义的名字的方法,从而提高代码的可读性和可维护性。
4.2 如何定义枚举?
在 C++ 中,使用 enum
关键字来定义枚举类型。语法如下:
enum 枚举名 {
枚举值1,
枚举值2,
...
};
其中,枚举名
是你为枚举类型取的名字,枚举值1
、枚举值2
等是你想定义的枚举值。例如:
enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
4.3 如何使用枚举?
定义枚举后,你可以声明枚举类型的变量,并将其赋值为枚举值之一。例如:
Day today = MONDAY;
你还可以直接使用枚举值,无需声明枚举类型的变量。例如:
cout << "Today is " << MONDAY << endl;
4.4 枚举值的默认赋值规则
在枚举中,每个枚举值都被赋予一个整数值,如果你没有显式地为枚举值指定值,那么它们将自动从0开始递增。第一个枚举值默认为0,后续枚举值依次递增。
4.5 枚举的优点
枚举可以提高代码的可读性,因为它们使得常量的含义更加明确。例如,使用 Day::MONDAY
要比使用数字 0 更能表达意图。此外,枚举还可以帮助你避免使用魔法数字,使得代码更易于理解和维护。
5,数据类型的使用示例
5.1 如何使用代码检查变量的数据类型
在 C++ 中,可以使用 typeid
运算符和 type_info
类来检查数据类型。这对于在运行时确定对象的实际类型非常有用。
以下是一个简单的示例,演示如何使用 typeid
来检查数据类型:
#include <iostream>
#include <typeinfo>
int main() {
int num = 10;
double pi = 3.14159;
std::string str = "Hello";
// 使用 typeid 和 type_info 来获取和打印变量的类型信息
std::cout << "num is of type: " << typeid(num).name() << std::endl;
std::cout << "pi is of type: " << typeid(pi).name() << std::endl;
std::cout << "str is of type: " << typeid(str).name() << std::endl;
return 0;
}
在这个例子中,typeid
返回一个 type_info
对象,该对象包含有关变量类型的信息,具体来说,返回一个 type_info 对象,该对象包含有关表达式类型的信息。.name()
方法返回一个 C-style 字符串,表示类型的名称。请注意,类型名称可能因编译器而异,因此结果可能会有所不同。
需要注意的是,typeid
和 type_info
主要用于在运行时获取类型信息。在编译时确定类型的情况下,可以使用模板和 decltype
等技术。需要注意的是,typeid 运算符的参数可以是任何表达式,包括变量、指针、引用等。它通常用于在运行时获取对象的实际类型,这在某些情况下对于进行类型检查和运行时多态非常有用。
5.2 类型转换符
在 C++ 中,有几种类型转换运算符可用于在不同类型之间进行转换。每种转换运算符都有其自己的用途和限制。以下是其中一些常见的类型转换运算符及其用法:
5.2.1 static_cast
static_cast是C++中一个类型转换运算符,用于在编译时进行类型转换。它是一种相对安全的转换,通常用于相似类型之间的转换。static_cast:用于执行静态类型转换,在编译时进行检查。它可以在合理范围内将一种类型转换为另一种类型,例如将整数转换为浮点数,或者将基类指针转换为派生类指针。
下面是static_cast 的基本用法:
new_type = static_cast<new_type>(expression);
其中 new_type 是想要转换的目标类型,而expression是要被转换的表达式或变量。
使用static_cast 的情况包括但不限于:
1,相似类型之间的转换:当需要在具有继承关系的类之间进行类型转换,或者在相关但不同类型之间进行转换时。static_cast是一种比较安全的选择
class Base {};
class Derived : public Base {};
Base* basePtr = new Derived;
Derived* derivedPtr = static_cast<Derived*>(basePtr);
2,显式类型转换:当你需要进行显式的类型转换时,而不是依赖隐式类型转换
double x = 3.14;
int y = static_cast<int>(x);
3,指针和数值类型之间的转换:当你需要在指针和数值类型之间进行转换时,static_cast也是一种安全的选择
int intValue = 42;
void* voidPtr = static_cast<void*>(&intValue);
4,避免编译器警告:在一些情况下,使用 static_cast 可以帮助避免编译器产生警告,特别是在一些窄化转换的情况
示例:
double d = 3.14;
int i = static_cast<int>(d); // 将 double 转换为 int
5.2.2 reinterpret_cast
reinterpret_cast是C++中的一种类型转换运算符,用于进行低级别的类型转换。通过用于将一个指针转换为另一种类型的指针,或者将一个指针转换为整数类型。因为它绕过了类型系统的一些安全检查,所以需要谨慎使用。
reinterpret_cast的基本语法如下:
new_type = reinterpret_cast<new_type>(expression);
其中,new_type 是你想要转换的目标类型,而 expression 是要被转换的表达式或变量。
一些reinterpret_cast的场景包括:
1,指针类型之间的转换:在一些特殊情况下,你可能需要将一个指针类型转换为另一种指针类型
int* intValue = new int(42);
char* charPtr = reinterpret_cast<char*>(intValue);
2,指针和整数类型之间的转换:将指针转换为整数类型,或者将整数类型转换为指针
int* intValue = new int(42);
uintptr_t intValueAsInt = reinterpret_cast<uintptr_t>(intValue);
需要注意的是,reinterpret_cast 提供了很大的灵活性,但也有潜在的危险。由于它绕过了类型系统的一些保护,使用时需要确保转换是合理和安全的。
通常情况下,首先应该考虑使用更安全的 static_cast,只有在确信没有更好的替代方案时,才考虑使用 reinterpret_cast。
示例:
int* ptr = reinterpret_cast<int*>(0x1234); // 将整数转换为指针
5.2.3 const_cast
const_cast:用于去除变量的常量属性,或者添加常量属性。主要用于解决函数重载时的二义性问题,以及在某些情况下修改 const 对象的值。
去除常量性的示例:
const int i = 10;
int& j = const_cast<int&>(i); // 去除变量的 const 属性
j = 20; // 可以修改 j,也会影响 i
在上面的例子中,const_cast
被用于去除 i
的常量性,从而可以通过 j
修改 i
的值。但是需要注意的是,这种做法虽然在语法上是合法的,但修改了 const 对象的值是一种未定义行为(undefined behavior),因此应该谨慎使用,并确保不会导致程序出现问题。
添加常量性的示例:
int value = 10;
const int& ref = value; // 引用 value 的 const 引用
int& mutableRef = const_cast<int&>(ref); // 添加常量性
mutableRef = 20; // 不会改变 ref 所绑定的对象 value 的值
在这个例子中,const_cast
被用于添加 ref
的常量性,从而确保 mutableRef
不能用于修改 value
的值。这在某些情况下可以帮助解决函数重载时的二义性问题。
需要注意的是,const_cast
用于去除或添加常量性时,都要确保所操作的对象实际上是可修改的。否则,如果尝试修改一个本来就是常量的对象,将会导致未定义行为。因此,在使用 const_cast
时应谨慎并确保操作的安全性。
5.2.4 dynamic_cast
dynamic_cast:主要用于在多态类型之间进行安全的向下转型。只能用于包含虚函数的类,用于将基类指针或引用转换为派生类指针或引用。dynamic_cast
在转换过程中会进行类型检查,如果转换是不安全的,则会返回一个空指针(对于指针转换)或抛出一个 bad_cast
异常(对于引用转换)。
dynamic_cast
只能用于具有虚函数的类(即多态类型)。因为在进行向下转型时,需要通过虚函数表来确定对象的实际类型,从而保证转型的安全性。
示例:
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {};
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
// 转换成功
// 使用 derivedPtr 操作 Derived 类型的对象
} else {
// 转换失败
}
delete basePtr;
return 0;
}
在上面的例子中,basePtr
是一个指向 Base
类型的基类指针,但它实际指向一个 Derived
类型的派生类对象。通过使用 dynamic_cast
将 basePtr
转换为 Derived*
类型的指针 derivedPtr
,我们可以安全地操作 Derived
类型的对象。
需要注意的是,如果 basePtr
指向的对象不是 Derived
类型的实例,而是其他类型的对象(或者是一个空指针),那么 dynamic_cast
将返回一个空指针,表示转换失败。因此,在使用 dynamic_cast
进行向下转型时,要确保转换是安全的,否则可能会导致程序运行时的错误。
5.2.5 reinterpret_cast和static_cast的主要区别
reinterpret_cast和static_cast是C++中两种不同类型的类型转换运算符,他们之间有些主要的区别。
1,类型转换的安全性
reinterpret_cast主要用于指针类型之间的转换,可以进行不安全的转换;它几乎不进行类型转换,例如将指针转换为整数,或者将一个指针类型转换为另一种不相关的指针类型。
static_cast主要用于基本类型之间的转换,提供一种相对安全的类型转换方法,例如基类指针到派生类指针的转换
char c = 'A';
int* p = reinterpret_cast<int*>(&c); // 危险的转换,可能导致未定义的行为
int i = 10;
double d = static_cast<double>(i); // 安全的转换,类型转换可行
2.编译时检查
static_cast 在编译时进行类型检查,如果转换是不合理或者不安全的,编译器会发生警告或错误
reinterpret_cast 在编译时几乎没有类型检查,它主要依赖于程序员的责任来确保类型转换的正确性
总的来说,static_cast更加安全,更易读,而reinterpret_cast则更加底层、灵活,但潜在的危险性更高,需要谨慎使用。在选择使用哪种类型转换时,要根据具体的情况和需求来权衡安全性和灵活性。
5.3 数据类型------int的使用误区
比如:我定义了一个int类型的变量a,然后我求 a/100,输出结果为0。
注意:如果我的a是一个整数,即int类型,那么a/100的结果可能是0,尤其是当a的值小于100时。因为在C++中,整数除法的结果仍然是整数,当a小于100,那么a/100的计算结果将被截断为0,因为整数除法会丢弃掉小数部分。即使a是大于等于100的正整数,结果也会是整数部分。
如果要获得精确的结果,那么则需要将其中一个运算符转换为浮点数,示例如下:
#include <iostream>
int main(){
int a = 75;
double result = static_cast<double>(a) . 100.0 ;
std::cout << result << std::endl;
return 0;
}
在这个例子中,我们使用 static_cast<double>(a)
将整数 a
转换为双精度浮点数,然后进行除法。这样,结果将包含小数部分。