- 使用编程语言进行编程时,我们需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置,这意味着,当创建一个变量时,就会在内存中保留一些空间。操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么。
【 1. 数据类型分类 】
- 变量的大小会根据编译器和所使用的电脑而有所不同。
/*
* 这个程序演示了有符号整数和无符号整数之间的差别
*/
#include <iostream>
using namespace std;
int main()
{
short int i; // 有符号短整数
short unsigned int j; // 无符号短整数
j = 50000;
i = j;
cout << i << " " << j;
return 0;
}
1.1 整型 int
- integer ,整数类型,通常用于存储普通整数。
名称 | 关键字(缩写) | 大小 | 范围 |
---|---|---|---|
有符号整型 | signed int(int) | 4 个字节 | -231= -2147483648 至 231-1=2147483647 |
无符号整型 | unsigned int(unsigned) | 4 个字节 | 0 至 232-1=4294967295 |
有符号长整型 | signed long int (signed long、long int、long) | 4 个字节 | -231= -2147483648 至 231-1=2147483647 |
无符号长整型 | unsigned long int(unsigned long) | 4 个字节 | 0 至 232-1=4294967295 |
有符号短整型 | signed short int(signed short、short int 、short) | 2 个字节 | -215= -32768 至 215-1=32767 |
无符号短整型 | unsigned short int(unsigned short) | 2 个字节 | 0 至 216-1=65535 |
1.2 字符型 char
- character,字符类型,用于存储 ASCII 字符。
- wchar_t 的来源:typedef short int wchar_t;故wchar_t 实际上的空间是和 short int 一样。
名称 | 关键字(缩写) | 大小 | 范围 |
---|---|---|---|
有符号字符型 | signed char(char) | 1个字节 | -27= -128 至 27-1=127 |
无符号字符型 | unsigned char | 1 个字节 | 0 至 28-1=255 |
宽字符型 | wchar_t | 2 或 4 个字节 | 1 个宽字符 |
1.3 浮点型 float / double
- 单精度浮点型:用于存储单精度浮点数。单精度是这样的格式,1 位符号,8 位指数,23 位小数,通常占用4个字节。
- 双精度浮点型:用于存储双精度浮点数。双精度是 1 位符号,11 位指数,52 位小数,通常占用 8 个字节。
- 多精度浮点型:是C99规范增加的新类型,只有支持C99的编译器才支持,根据编译器实现不同,有占用8,10,12字节和16字节四种。
名称 | 关键字 | 大小 | 范围 |
---|---|---|---|
单精度浮点型 | float | 4 个字节 | 1.17549e-38至3.40282e38 |
双精度浮点型 | double | 8 个字节 | 2.22507e-308至 1.79769e308 |
多精度浮点型 | long double | 8个字节 | 2.22507e-308至 1.79769e308 |
1.4 布尔型 bool
名称 | 关键字 | 大小 | 范围 |
---|---|---|---|
布尔型 | bool | 1个字节 | True 或 False |
1.5 无类型 void
名称 | 关键字 | 大小 | 范围 |
---|---|---|---|
无类型 | void | \ | \ |
1.7 其他类型
- 一些数据结构,如结构体、枚举等等。
1.8 类型占用大小输出测试
//输出电脑上各种数据类型的大小。
#include<iostream>
#include<string>
#include <limits>
using namespace std;
int main()
{
cout << "type: \t\t" << "************size**************"<< endl;
cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);
cout << "\t最大值:" << (numeric_limits<bool>::max)();
cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;
cout << "char: \t\t" << "所占字节数:" << sizeof(char);
cout << "\t最大值:" << (numeric_limits<char>::max)();
cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;
cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);
cout << "\t最大值:" << (numeric_limits<signed char>::max)();
cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;
cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);
cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();
cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;
cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);
cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();
cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;
cout << "short: \t\t" << "所占字节数:" << sizeof(short);
cout << "\t最大值:" << (numeric_limits<short>::max)();
cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;
cout << "int: \t\t" << "所占字节数:" << sizeof(int);
cout << "\t最大值:" << (numeric_limits<int>::max)();
cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;
cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);
cout << "\t最大值:" << (numeric_limits<unsigned>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;
cout << "long: \t\t" << "所占字节数:" << sizeof(long);
cout << "\t最大值:" << (numeric_limits<long>::max)();
cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;
cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);
cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;
cout << "double: \t" << "所占字节数:" << sizeof(double);
cout << "\t最大值:" << (numeric_limits<double>::max)();
cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;
cout << "long double: \t" << "所占字节数:" << sizeof(long double);
cout << "\t最大值:" << (numeric_limits<long double>::max)();
cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;
cout << "float: \t\t" << "所占字节数:" << sizeof(float);
cout << "\t最大值:" << (numeric_limits<float>::max)();
cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;
cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);
cout << "\t最大值:" << (numeric_limits<size_t>::max)();
cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;
cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;
// << "\t最大值:" << (numeric_limits<string>::max)() << "\t最小值:" << (numeric_limits<string>::min)() << endl;
cout << "type: \t\t" << "************size**************"<< endl;
return 0;
}
【 2.类型限定符 】
2.1 存储类限定符
auto
- 根据初始化表达式 自动推断被声明的变量的类型。
- 声明函数时函数返回值的占位符。
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
register
- 用于定义 存储在寄存器中而不是 RAM 中的局部变量 。这意味着 变量的最大尺寸等于寄存器的大小(通常是一个词),且 不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
- 寄存器只用于需要快速访问 的变量,比如计数器。还应注意的是,定义 ’ register ’ 并不意味着变量将被存储在寄存器中,它 意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
{
register int miles;
}
static
- 指示编译器 在程序的生命周期内保持局部变量的存在,不需要在每次它进入和离开作用域时进行创建和销毁 。
- 当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
- 在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
#include <iostream>
/* 全局变量 */
static int count = 10;
// 函数
void func( void )
{
static int i = 5; // 局部静态变量
i++;
std::cout << "变量 i 为 " << i ;
std::cout << " , 变量 count 为 " << count << std::endl;
}
int main(void)
{
while(count--)
{
func();
}
return 0;
}
extern
- 用来在另一个文件中声明一个全局变量或函数,使得其可以在多个文件中被共享使用。当有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。
第一个文件:main.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
第二个文件:support.cpp
#include <iostream>
extern int count; //第二个文件中的 extern 关键字用于声明已经在第一个文件 main.cpp 中定义的 count
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
mutable
- 仅适用于类的对象,允许对象的成员替代常量。也就是说, mutable 成员可以通过 const 成员函数修改。
thread_local
- 被声明的变量仅可在它在其上创建的线程上访问。
- 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
- 可以与 static 或 extern 合并。
- 仅应用于数据声明和定义,不能用于函数声明或定义。
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
2.2 数据大小限定符
- C++ 允许在 char、int 和 double 数据类型前放置 大小修饰符。修饰符用于改变基本类型的含义,所以它更能满足各种情境的需求。
- C++默认是 有符号的 。例如 signed int a; 和 int a; 是一样的。
- 修饰符 signed、unsigned、long 和 short 可应用于整型;
修饰符 signed 和 unsigned 可应用于字符型;
修饰符 long 可应用于双精度型;
修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。
2.3 其他限定符
限定符 | 含义 |
---|---|
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。 |
restrict | 由 restrict 修饰的指针是唯一 一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
【 3. typedef 类型声明 】
- 可以使用 typedef 为一个已有的类型取一个新的名字。
// 下面的语句会告诉编译器,feet 是 int 的另一个名称。
typedef int feet;
//下面的声明是完全合法的,它创建了一个整型变量 distance:
feet distance;
【 4. 数字型数据 】
4.1 整数数据
- 整数的加法、减法、乘法和数学中的算术运算一致,而除法略有不同。例如:
#include <stdio.h>
int main(void)
{
int a = 23, b = 8, c; // 声明整型变量 a、b、c,并对 a、b 进行初始化
c = a / b; // 计算 a 除以 b 并将结果存入 c 中
printf("%d\n",c); // 输出 c 的值
}
- 说明:
23/8的结果为2,而不是2.875。这是因为两个整数相除,采用的是整数除法,结果也是整数。而且不是四舍五入的整数,而是直接截掉小数部分的整数。
4.2 浮点型数据
- 对整数间除法的改善:如果要得到除法结果的小数部分,需要使用浮点数除法。
- 只要除数和被除数其中有一个是浮点数,系统就会使用浮点数除法。例如:
#include <stdio.h>
int main(void)
{
float num = 123 / 23.0;
printf("%f\n",num);
}
- 说明:
算式中23.0为浮点数,所以123/23.0采用浮点数除法,结果也是浮点数(存入到浮点变量 num 中)。使用 printf 输出时,采用的转换说明符也是相应的%f。
4.3 不同数字类型的混合运算
- 表达式中操作数的类型不同时,系统会自动进行 隐式类型转换 ,使表达式中的数据类型一致。分为以下两种情景:
-
在算术表达式中隐式类型转换规则为: 把不同类型的数据转换成精度最高、占用内存最多的 那个数据的类型。比如,如果两个操作数一个是整型数,一个是浮点数,系统会先对整型数进行类型转换,转换为等值的浮点数,然后再计算。
-
在赋值表达式中,会 自动将赋值运算符右边表达式的值的类型转换成左边变量的类型,这种转换可能会丢失数据的精度。
// 【 隐式类型转换举例】:
#include <stdio.h>
int main(void)
{
float x = 2 + 3.4; // 隐式类型转换,系统会先把整数 2 转换成浮点数 2.0,然后再做加法运算
printf("%f\n",x);
}
- 当然,对应的肯定有 强制类型转换(也叫显式类型转换),,它把表达式值的类型强制转换成指定的类型。
// 【 显示类型转换(强制类型转换) 】
//先将3转换成 double 类型的 3.0 ,
//然后计算3.0/2,由于2是整型,计算之前需要进行隐式类型转换,所以2也将转换成 double 类型的2.0。
#include <stdio.h>
int main(void)
{
printf("%lf\n",(double)3/2);
}
【 5. 字符/字符串 型数据 】
- 存储英文字母需要用到字符型变量。C 和 C++ 将字符类型看成是一种短整型,每个字符数据对应一个整数值。
- 字符变量用来存储字符,一个字符占1个字节(8位),字符存储的其实是 ASCII 码表中所对应的整数,这些整数以 0、1 串来表示。
- 例如:‘A’的 ASCII 码是65,对应的8位二进制 0-1 串是 01000001。
5.1 基本原理
-
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 ‘\0’ 终止 的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
-
字符串、字符串数组、字符数组的定义
在定义字符串时,指定的字符串长度=字符数量+1。
char s11[] = "123456"; //字符串
char s12[7] = "123456"; //字符串
char s13[7] = { "123456" }; //字符串
char s14[] = { "123456" }; //字符串
char s2[2][7] = { "123","123456" }; //字符串数组
char s31[3] = {'1','3','7'}; //字符数组
char s32[] = { '1','3','7' }; //字符数组
- 下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 “Hello” 的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
//依据数组初始化规则,您可以把上面的语句写成以下语句:
char greeting[] = "Hello";
5.2 字符的加减运算
-
由于 C 和 C++ 将字符类型作为整数处理,所以可以通过修改一个字符变量中的整数值来修改其中的字符。
-
在 ASCII 码表中26个大写英文字母是连续顺序排列的,也就是说字符‘B’代表的整数比字符‘A’代表的整数值大1,…… ,字符‘Z’代表的整数值比字符‘Y’代表的整数值大1,所以可以有下面的程序
#include <stdio.h>
#include <iostream>
using namespace std;
int main(void)
{
char ch = 'B'; // 定义字符变量 ch 并初始化为字符 B ,ch中存储的是 B 对应的整数
ch = ch + 3; // 将 ch 中的整数加 3
cout << ch << endl;
return 0;
}
5.3 字符的位运算
- 我们可以使用按位与运算判断一个字符的二进制表示(共8位)的某一位是1还是0。
- 实例:
输出字符变量 c 的二进制表示的第7位的值(c 的二进制表示有8位,从左到右分别为第 1~8 位):
cout << (int)(bool)(c & 0x02);
//该表达式有三个运算符:强制类型转换( int )、强制类型转换( bool )和按位与运算符 &。
- 因为括号的原因,表达式 先计算c & 0x02 。0x02 是十六进制的02,其二进制表示为 00000010 。将 c 和 0x02 进行按位与,除了第7位外,不需要管 c 的其它位是什么,因为 0x02 除了第7位,其它位都是0。那么就有,如果 c 的第7位如果是1,则按位与的结果就是 00000010,否则结果为 00000000。
- 然后再进行两个类型转换运算,优先级相同,右结合,所以 再计算(bool)(c & 0x02),即将计算结果转换为 bool 类型。如果c & 0x02的值为 00000000,则结果为 false,否则结果为 true(即只要有一位不为0,则整个十六进制数转换后即为 true)。最后再把计算结果转换为 int 类型,即如果前面的计算结果是 true,转换结果就是1,否则是0。
- 所以整个表达式的运算效果是:如果 c 的第7位为0,就输出0,第7位数为1则输出1。以此类推,我们就可以计算出 c 对应的二进制8位 0-1 串了。
5.4 字符函数
- 头文件: #include <ctype.h>(C)或 #include <cctype>(C++) 。
函数名称 | 作用 |
---|---|
isalnum() | 判断是否是字母或者数字字符 |
isalpha () | 判断是否是字母字符 |
isdigit () | 判断是否是数字字符(0~9) |
ispunct () | 判断是否是标点符号字符 |
isspace () | 判断是否是标准空白(空格、制表符、换行等) |
islower () | 判断是否是小写字母字符 |
isupper () | 判断是否是大写字母字符 |
tolower () | 转换成小写字母字符 |
toupper () | 转换成大写字母字符 |
5.5 字符串函数
- 头文件: #include <string.h>(C)或 #include <cstring>(C++)。
函数 | 功能 |
---|---|
strcpy(s1, s2); | 复制字符串 s2 到字符串 s1。 |
strcat(s1, s2); | 连接字符串 s2 到字符串 s1 的末尾。 |
strlen(s1); | 返回字符串 s1 的长度。 |
strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
实例 - 字符串复制
#include <iostream>
using namespace std;
int main()
{
char s1[7] = "123456";
char * s2 = new char[7];
//或者 char s2[7];
strcpy_s(s2,7, s1);
cout << s2 << endl;
return 0;
}
5.5 string 类
- C++ 标准库提供了 string 类类型,支持上述C语言字符串函数所有的操作,另外还增加了其他更多的功能。
- 头文件 #include <string>(C++)。
- string 字符串的起始下标仍是从 0 开始,string 字符串也可以像C风格的字符串一样 按照下标来访问其中的每一个字符。
- 常用 string 类 操作函数
函数 | 功能 |
---|---|
s1=s2 | 复制string 类 s2 到string 类 s1。 |
s1+s2 | 连接string 类 s2 到string 类 s1 的末尾,并返回。 |
s1.size(); s1.length(); | 返回string 类 s1 的长度。 |
可以使用==、!=、<、>等运算符来比较两个string 类的大小 | 如果为真返回1,否则返回0。 |
- 实例:
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "Hello";
string str2 = "World";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,输出str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}