在C语言中,数据类型指的是用于声明不同类型变量或者函数的一个广泛的系统或者抽象。变量类型决定了变量存储占用的空间,以及如何解析存储的位模式。也是说:(1)数据类型可以理解为固定内存大小的别名;(2)数据类型是创建变量的模子,具体使用哪种磨具(包含自定义)需要基于实际问题抽象结果确定。
C中可以分为以下几种数据类型:
序号 | 类型与描述 |
---|---|
1 | 基本类型: 它们是算术类型,包括两种类型:整数类型和浮点类型。 |
2 | 枚举类型: 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。 |
3 | void 类型: 类型说明符 void 表明没有可用的值。 |
4 | 派生类型: 它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。 |
数组类型和结构类型统称为聚合类型,函数类型指的是函数的返回值类型。
1. 整数类型
对于每种类型的整数类型,可以使用sizeof来确定其大小。下表列出了关于标准整数类型的存储大小和值范围的细节:
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
2. 浮点类型
下表列出了关于标准浮点类型的存储大小、值范围和精度的细节:
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
头文件 float.h 定义了宏,在程序中可以使用这些值和其他有关实数二进制表示的细节。下面的实例将输出浮点类型占用的存储空间以及它的范围值:
3. void 类型
void 类型指定没有可用的值。它通常用于以下三种情况下:
序号 | 类型与描述 |
---|---|
1 | 函数返回为空 C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); |
2 | 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);参数为空和任意参数或者变可变参数是两个不同的概念,没有参数值得是在进行函数调用的时候直接使用没有任何入参。 |
3 | 指针指向 void 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。void类型指针需要注意:(1) C语言规定只有相同类型的指针才可以相互赋值; (2)void* 指针作为左值用于“接收”任意类型的指针,这个特点类型的指针在我们试图将函数变成一个较为通用的二级指针函数时非常有用; (3) void* 指针作为右值使用时需要进行强制类型转换 |
3.1 void函数与返回值说明
#include <stdio.h>
void f(void) //无参无返回值
{
}
void main()
{
f(); //ok
// 不存在void变量,C语言没有定义void究意是多大内存的别名,没有void标尺,无法在内存中裁出大小
void var; //error,不可以是void型的变量
void array[5]; //error,同上
void* pv; //ok,void*的指针是可以的。
// 在ANSI C编译器中无法通,支持GNU标准的gcc下为void大小为1,是合法的
printf("sizeof(void)=%d\n",sizeof(void));
}
3.2 void指针
int* pI = (int*)malloc(sizeof(int));
char* pC = (char*)malloc(sizeof(char));
void* p = NULL;
int* pni = NULL;
char* pnc = NULL;
p = pI; //ok,void*指针p可接收任何类型的指针
pni = p; //error,void*须强制类型转换,即pni =(int*)p;
p = pC; //ok
pnc = p; //error,应为pnc=(char*)p;
#include <stdio.h>
void MemSet(void* src, int length, unsigned char n)
{
unsigned char* p = (unsigned char*)src;
int i = 0;
for(i=0; i<length; i++)
{
p[i] = n;
}
}
int main()
{
int a[5];//这里可以是任何其他类型,如char a[5];double a[5]等。
int i = 0;
MemSet(a, sizeof(a), 0);
for(i=0; i<5; i++)
{
printf("%d\n", a[i]);
}
return 0;
}
4. 类型转换
数据类型转换:C 语言中如果一个表达式中含有不同类型的常量和变量,在计算时,会将它们自动转换为同一种类型;在 C 语言中也可以对数据类型进行强制转换;类型转换可以是隐式的,由编译器自动执行,也可以是显式的,通过使用强制类型转换运算符来指定。在编程时,有需要类型转换的时候都用上强制类型转换运算符,是一种良好的编程习惯。
4.1 自动转换规则:
(1)隐式类型的转换——编译器主动进行的类型转换
char c = 0; //变量c占用1个字节
short s = c; //c到s隐式类型转换
int i = s; //s到i隐式类型转换
long l = i; //i到l隐式类型转换
▲注意:低类型到高类型的隐式类型转换是安全的,不会产生截断;高类型到低类型的隐式类型转换是不安全的,导致不正确的结果
(2)隐式类型转换的发生点
①算术运算式中,低类型转换为高类型
②赋值表达式中,表达式的值转换为左边变量的类型
③函数调用时,实参转换为形参的类型
④函数返回值,return表达式转换为返回值类型
- a)浮点数赋给整型,该浮点数小数被舍去;
- b)整数赋给浮点型,数值不变,但是被存储到相应的浮点型变量中;
4.2. 强制类型转换形式: (类型说明符)(表达式) (type_name) expression
(1)强制类型转换的语法:(Type)var_name或(Type)value
(2)强制类型转换的结果
①目标类型能够容纳目标值:结果不变
②目标类型不能容纳目标值:结果将产生截断
(3)注意:不是所有的强制类型转换都能成功,当不能进行强制类型转换,编译器将报错。
4.5. C++强制类型转换
类型转换是项目中难以避免的,但是C语言转换过于粗暴,编译器难以帮助进行错误判定,出错以后难以定位。C++中新增了四种强子类型转换static_cast、const_cast、reinterpret_cast、dynamic_cast。其相应的应用场景:
(1)static_cast:用于基本类型之间的转换,用于有继承关系类对象之间的转换,不能用于基本类型指针间的转换。
(2)const_cast:用于指针或者引用之间的强制类型转换,目标去除对象的只读属性
(3)reinterpret_cast:用于指针类型间的转换或者指针和整数之间的类型转换
(4)dynamic_cast:用于有继承关系或者交叉关系之间类指针之间的转换,具有类型检查的功能,同时需要虚函数的支持
#include <stdio.h>
void static_cast_demo()
{
int i = 0x12345;
char c = 'c';
int* pi = &i;
char* pc = &c;
c = static_cast<char>(i);
pc = static_cast<char*>(pi);
}
void const_cast_demo()
{
const int& j = 1;
int& k = const_cast<int&>(j);
const int x = 2;
int& y = const_cast<int&>(x);
int z = const_cast<int>(x);
k = 5;
printf("k = %d\n", k);
printf("j = %d\n", j);
y = 8;
printf("x = %d\n", x);
printf("y = %d\n", y);
printf("&x = %p\n", &x);
printf("&y = %p\n", &y);
}
void reinterpret_cast_demo()
{
int i = 0;
char c = 'c';
int* pi = &i;
char* pc = &c;
pc = reinterpret_cast<char*>(pi);
pi = reinterpret_cast<int*>(pc);
pi = reinterpret_cast<int*>(i);
c = reinterpret_cast<char>(i);
}
void dynamic_cast_demo()
{
int i = 0;
int* pi = &i;
char* pc = dynamic_cast<char*>(pi);
}
int main()
{
static_cast_demo();
const_cast_demo();
reinterpret_cast_demo();
dynamic_cast_demo();
return 0;
}
根据我们上面的分析,第11行是错误的,22行是错误的,47行是错误的,54行是错误的。
一、C强制类型转换
格式: TYPE a = (TYPE) b;
缺点: 1.多个转换之后无法查找具体的转换类型,即转换不明确;
2.不能进行错误检查,容易出错。
二、C++四种类型转换方式:
(一)、const_cast
简介: const_cast是一个基于C语言编程开发的运算方法,其主要作用是:修改类型的const或volatile属性。使用该运算方法可以返回一个指向非常量的指针(或引用),就可以通过该指针(或引用)对他的数据成功元任意改变。
格式: type-id a = const_cast <type-id>( expression )
说明:上述格式中,除了const或volatile修饰之外,type_id和expression的类型是一样的。(1).常量指针被转换成非常量指针,并且仍然指向原来的对象; (2).常量引用被转换为非常量的引用,并且仍然指向原来的对象。(3).const_cast一般用于修改形如const char *p指针。
用法:
说明: volatile和const类似。
(二)、static_cast
简介: static_cast 是一个C++运算符,功能是把一个表达式转换为某种类型,但没有运行时类型检查来保证传唤的安全性。
格式: type-id a = static_cast <type-id>( expression )
用法:
1.用于类层次结构中基类(父类)和派生类(子类)之间的指针或引用的和转换。进行向上转换(将派生类的指针或引用转换成基类表示)是安全的;进行向下转换(将基类指针或者引用转换为派生类表示)时,由于没有动态类型的检查,所以是不安全的。
2.用于基本数据类型之间的转换,如把int转换为char,把int转换为enum。这种转换的安全性也要开发人员来保证。
3.把空指针转换成目标类型的空指针。
4.把任何类型的表达式转换为void类型。
注意事项: static_cast 不能转换掉expression的const、volatile或者__unaligned属性。
(三)、dynamic_cast
简介: dynamic_cast是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。dynamic_cast运算符涉及到编译器的属性设置,而且牵扯到的面向对象的多态性跟程序运行时的状态也有关系,所以不能完全的使用传统的替换方式来代替。但是也因此它最常用,是最不可缺少的一个运算符。
格式: type-id a = dynamic_cast<type-id>( expression )
说明:该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*;如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。dynamic_cast运算符可以再执行期决定真正的类型。如果downcast是安全的(也就是说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(即基类指针或者引用没有指向一个派生类对象)。--->此部分来源于百度
用法:
1.dynamic_cast在类层次间进行向上转换时,效果和static_cast一样。
2.dynamic_cast在类层次间进行向下转换时,具有类型检查功能,更安全。
说明: Parent类一定要有虚函数,否则会编译出错;static_cast没有这个限制。
C++面向对象的思想中,虚函数是实现多态的必要路径,如果一个类有虚函数,则编译器会构建出一个虚函数表(virtual method table)来指示这些函数的地址,如果之类中重写了该虚函数,则虚函数表会将该函数指向新的地址。如果基类有虚函数表,子类会自动继承该表。另外:如果要用继承,那么一定要让析构函数是虚函数;如果一个函数时虚函数,那么在子类中也要是虚函数。
3.dynamic_cast的交叉转换。
说明: 对于单继承和多继承来说 : 单继承中,Base* pD =
new
Derived1;,可以用dynamic_cast和static_cast转换pd为Derived1;如果是多继承,Derived1继承Base和Base1,则只能用dynamic_cast将pd转换为Base1类型。
(四)、reinterpret_cast
简介: reinterpret_cast,是C++里强制类型转换符。
格式: type-id a = reinterpret_cast<type-id>( expression )
说明:上述格式中,type_id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换为一个整数,也可以把一个整数转换为一个指针(先把一个指针转换为一个整数,再把该整数转换为原来类型的指针,还可以得到原来的指针值)。
用法:
操作符修改了操作数类型后,仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。---》危险
举个栗子:
4.4. 小结
(1)强制类型转换由程序员负责完成
①转换可能产生截断;
②转换不区分类型的高低
③转换不成功时,编译器给出错误信息
(2)隐式类型转换由编译器自动完成
①低类型向高类型的转换是安全的
②高类型向低类型的转换是不安全的
★标准C编译器的类型检查是比较宽松的,因此隐式类型转换可能带来意外的错误
5. typedef 自定义类型别名
我们可以使用typedef给任意的组合体取名为某种实际项目中使用的类型,方便在后续的代码书写中使用这个整体来进行数据联合体操作。用法:typedef type new_name。
typedef用于给一个己经存在的数据类型重命名,实际项目使用过程中我们一般会为我们项目从新命名unsigned int等类型变量,但是这个基本就是最基本算是入门的东西,不是任何高超的技术。但是typedef本质上不能产生新的类型,即便是结构体也是一样的,只是为了我们后期方便使用使用typedef给结构体定义另外一个名字。typedef重命名的类型可以在typedef语句之后定义,不能被unsigned和signed修饰(即不能再定义为无符号或有符号)。
枚举类型,枚举类型是一种特别数据类型,其内部构成是我们之前讲到的基本数据类型,这里通过枚举的方式也是将某一部分常用的常量数据集合在一起,方便代码书写过程使用。当然也可以结合typedef来对这个结构体进行重命名,将这个结构体重新定义为一个别名的数据类型,方便代码书写过程使用。
#include <stdio.h>
typedef int Int32;
//先定义类型后重命名类型
struct _tag_point
{
int x;
int y;
};
typedef struct _tag_point Point;
//定义一个未命名类型,并用typedef重命名
typedef struct
{
int length;
int array[];
} SoftArray;
//先重命名,再定义类型——看似不合法、别扭,但实际上正确的
//可理解为,typedef并不给进行类型检查,只是简单去给一种类型起个别名,
//如,本例本struct _tag_list_node重命名为ListNode,以后编译过程中遇到
//ListNode,就用其实际的类型为struct _tag_List_Node来替换.
typedef struct _tag_list_node ListNode; //先重命名
struct _tag_list_node //再定义类型
{
ListNode* next;
};
int main()
{
Int32 i = -100; // int
unsigned Int32 ii = 0; //error,不能为重命名类型指定义为signed或unsigned
Point p; // struct _tag_point
SoftArray* sa = NULL;
ListNode* node = NULL; // struct _tag_list_node*
return 0;
}
6. bool数据类型
C语言中没有bool数据类型,表达式值为0表示假,非0表示真。所以在条件表达式判断语句(if(...))非常灵活,甚至都可以是指针类型条件表达式。为了使程序更清晰,在C语言中我们常常使用如下宏定义类标示C语言bool数据
typedef BYTE BOOL
#define TURE 1
#define FALSE 0
C++在C语言的基本类型系统之上增加了bool。C++中的bool可取的值只有true和false(在程序编写上使用true或false,在编译器内部还是用1或0)。理论上bool只占用一个字节。需要注意:true代表真值,编译器内部用1来表示;false代表非真值,编译器内部用0来表示。在C++中为了兼容C语言,C语言中非0的结果会被强制转换为C++中的TRUE,这个是由编译器完成。