C++使用前一(两)位来标识数字常量的基数:
使用 cout 输出时可以通过控制符 dec(十进制)、otc(八进制)、hex(十六进制) 指定输出格式
#include
#include
int main() {
using namespace std;
cout << oct; // 使用八进制格式输出
cout << 16 << endl; // 输出 20
return 0;
}
如果想要以二进制输出,可以用 bitset< size >(int) 函数
#include
#include
int main() {
cout << "54的二进制数 = " << bitset<8>(54) << endl; // 输出 00110110
return 0;
}
#include
int main() {
using namespace std;
int x;
cout << "The expression x = 100 has the value ";
cout << (x = 100) << endl;
cout << "Now x = " << x << endl;
cout << "The expression x < 3 has the value ";
cout << (x < 3) << endl;
cout << "The expression x > 3 has the value ";
cout << (x > 3) << endl;
cout.setf(ios_base::boolalpha); // 转成输出 bool 值
cout << "The expression x < 3 has the value ";
cout << (x < 3) << endl;
cout << "The expression x > 3 has the value ";
cout << (x > 3) << endl;
return 0;
}
输出
The expression x = 100 has the value 100
Now x = 100
The expression x < 3 has the value 0
The expression x > 3 has the value 1
The expression x < 3 has the value false
The expression x > 3 has the value true
通常,
cout
在显示bool
值之前将它们转换为int
,但cout.setf(ios::boolalpha)
函数调用设置了一个标记,该标记命令cout
显示true
和false
,而不是1和0。
结构是 C++ 中的一种复合数据结构。一个结构里面可以有多个不同类型的变量。
如:
#include
using namespace std;
// 声明结构
struct Piza
{
char company[20];
float diameter;
float height;
};
int main() {
Pize pize_one = {"Xingfuxibing", 35.6f, 205.6f}; // 声明同时初始化
Pize pize_two; // 声明
pize_two = {"Xingfuxibing", 35.6f, 205.6f}; // 初始化
return 0;
}
位字段用于指定使用的位数,常用于整形和枚举。平常编程一般用不着,多用于低级编程。
#include
#include
#include
// 声明结构 structureOne
struct structureOne
{
unsigned int num : 4; // 格式: 变量名:位 // 指定变量 num 的位数 = 4
unsigned int: 4; // 匿名字段,提供字段间距(增加了结构的长度)
bool flag1 : 1; // 原 bool 类型占用 1 字节 8 位的长度,现指定只占 1 位
};
int main() {
using namespace std;
cout << "int 的默认数位 = " << sizeof(int) * 8 << " bit" << endl;
structureOne one = { 54,false,true };
cout << "one.num = 54" << endl;
unsigned int num2 = 54;
cout << "54的二进制数 = " << bitset<8>(num2) << endl; // 输出 0011 0110
cout << "one.num = " << one.num << endl; // 输出
return 0;
}
说明:
因为数据结构中指定了 num 的位数为4,原二进制数 0011 0110 从低位取起,取得的二进制数为 0110,所以输出十进制数 one.num = 6
共同体和结构差不多,也可以构建一个多变量数据结构,但特点是里面的变量共享内存,即只有以变量类型中最长的类型为长度的那一块内存。基于此特点,共同体只能同时存储一个变量(保证一个变量的准确)。
如:
共同体内有 int a 和 double b 两个变量,先给 a 赋值,后在给 b 赋值时,则 a 的值可能被覆盖。
反之如果先给 b 赋值,在给 a 赋值,b 的值可能不会被覆盖。
具体要看数据类型的长度和赋值的大小而定,但总的来说能保证的就其中一个变量,这也是共同体设计出来的意义。
#include
#include
using namespace std;
// 共同体
union unionOne
{
int int_val;
double double_val;
};
int main() {
unionOne one;
one.double_val = 87.54; // 给 double_val 赋值
cout << "double_val = " << one.double_val << endl; // 87.54
cout << "intVal = " << one.int_val << endl; // 1546188227
one.int_val = 90; // 给 int_val 赋值
cout << "double_val = " << one.double_val << endl; // 87.54
cout << "int_val = " << one.int_val << endl; // 90
one.double_val = 45.65; // 给 double_val 赋值
cout << "double_val = " << one.double_val << endl; // 45.65
cout << "int_val = " << one.int_val << endl; // 858993459
return 0;
}
在 double_val 有值的情况下,给 int_val 赋值后,double_val 的值没有被覆盖。
后面再给 double_val 赋值后,int_val 的值被覆盖了。
多次测试后得出结论: 给小类型变量赋值大类型变量不一定会被覆盖,但给最大类型变量赋值小类型变量一定会被覆盖。
共用体的用途:
当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如,假设管
理一个小商品目录,其中有一些商品的 id 为整型,而另一些的 id 为字符串。在这种情况下,可以这样做:
struct goods{
char brand[20]:
int type:
union id{ // id 共同体
long id_num; // long 类型 id
char id_char[20]; // 字符串 id
}id_val; // id 变量 id_val
};
// 初始化
goods goodOne;
if(goodOne.type==1) // 根据类型选择一种 id 类型存储
cin > goodOne.id_val.id_num; // 存入 int 型 id 变量 id_num
else
cin > goodOne.id_val.id_char: // 存入 char 型 变量 id_char
C++的enum 工具提供了另一种创建符号常量的方式,这种方式可以代替const。.
#include
#include
using namespace std;
int main() {
enum color{red,orange,yellow,green}; // 定义枚举类型 color
cout << "red = " << red << endl; // red = 0
cout << "orange = " << orange << endl; // orange = 1
cout << "yellow = " << yellow << endl; // yellow = 2
cout << "green = " << green << endl; // green = 3
int num = yellow;
cout << "num = " << green << endl; // 赋值成功,枚举自动提升为 int 类型
color colorOne; // 定义一个枚举 color 类型的变量 colorOne
colorOne = orange; // 合法
cout << "colorOne = " << colorOne << endl; // colorOne = 1
//colorOne = 1; // 非法,枚举类型无法转换成整型
colorOne = color(2); // 合法,整型强转成 color 枚举
cout << "colorOne = " << colorOne << endl; // colorOne = 2
return 0;
}
总结:
枚举的创建与结构类似,里面的每个变量都是一个 enum 对象,是一个常量 ,而非字符串或数值
枚举会自动提升为整型,整型转换成枚举时需要强转。推测就是枚举是一种类似于 tinyint 的短整型数据,因此需要强转 int 。
通常枚举用来定义常量,可以选择匿名的方式 enum { red,orange,yellow,green };
初始化时可以直接给枚举对象赋值,枚举对象的值必须是整型(包括 int 和 long 等),不赋值则默认从 0 开始赋值。
enum {red = 10, orange = 12, yellow = 14, green = 16 };
也可以只给某些枚举对象赋值
enum {red, orange = 12, yellow, green = 16 };
枚举对象的默认赋的值是前一个对象的值 + 1,像这里的 red = 0,yellow = 13
多个枚举对象的值可以相同
enum {red = 16, orange = 12, yellow = 12, green = 16 };
枚举的取值范围
最初,对于枚举来说,只有声明中指出的那些值是有效的。不过,C+现在通过强制类型转换,增加了可赋给枚举变量的合法值。每个枚举都有取值范围(range),通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量,即使这个值不是枚举值。例如,假设 bits 和 myflag 的定义如下:
enum bits{one = 1,two = 2,four = 4,eight = 8};
bits myflag;
则下面的代码将是合法的:
myflag bits (6); // 合法,因为 6 在 bits 枚举的取值范围内
其中6不是枚举值,但它位于枚举定义的取值范围内。
取值范围的定义如下。
首先,要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。
例如,前面定义的 bigstep 的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0。否则,采用与寻找上限方式相同的方式,但加上负号。
例如,如果最小的枚举量为-6,而比它小的、最大的2的幂是-8(加上负号),因此下限为-7。选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用1个字节或更少的空间。而对于包含long类型值的枚举,则使用4个字节。
指针是一个变量,其存储的是值得地址,而不是值本身。通常说的指针
其实是指指针变量
,指针
就是指内存单元的编号(地址),而指针变量
是存储这个指针
的变量!下面讨论的都是指针变量
,看到指针
请脑补成指针变量
。
如何找到一个常规变量的地址?只需要对变量应用地址操作符 &
即可。
#include
#include
int main() {
using namespace std;
int num = 5;
string str_one = "嘻嘻嘻";
cout << "num 地址:" << &num <<endl; // num 地址:012FF6D0
cout << "str_one 地址:" << &str_one << endl; // str_one 地址:012FF6AC
return 0;
}
*
操作符称为间接值操作符或解除引用操作符,编译器会智能区分操作符用作 解除引用 还是 乘法。
对于一个指针变量来说,指针名表示 地址,指针变量的间接值表示存储在该地址的 值。
#include
#include
int main() {
using namespace std;
int num1 = 5;
string str_one = "嘻嘻嘻";
cout << "num 地址:" << &num1 <<endl; // num 地址:012FF6D0
cout << "str_one 地址:" << &str_one << endl; // str_one 地址:012FF6AC
int num = 6;
int *p_num; // 声明指针
p_num = #
// 输出值
cout << "num = " << num << endl; // num = 6
cout << "*p_num = " << *p_num << endl; // *p_num = 6
// 输出地址
cout << "&num = " << &num << endl; // &num = 006FFB24
cout << "p_num = " << p_num << endl; // p_num = 006FFB24
// 用指针变量修改值
*p_num = *p_num + 1;
cout << "after *p_num + 1, num = " << num << endl; // num = 6
return 0;
}
1.变量
num
和指针变量p_num
只不过是同一枚硬币的两面:
- 普通变量
num
默认引用的是数据的值,通过使用&
操作符,如&num
的方式来获取数据的地址。- 指针变量
p_num
默认引用的是数据的地址,通过使用*
操作符,如*p_num
的方式来获得数据的值。2.由于
*p_num
指向数据的值 ,num
引用的也是数据的值,因此可以说*p_num
和num
完全等价。
3.可以像使用普通变量
那样使用*p_num
,甚至可以将值赋给*p_num
,这样做将修改指向的值。
int *p_num
、double *p_double
、char* p_char
。类型* 指针名
和类型 *指针名
都是合法的。指针变量声明时之所以要指定类型是为了确定指针增量
,一个指针只能指向一个地址(也就是一个字节
),当指向int
时,int
有四个字节
,那么只能指向它的首地址。解引用时编译器会根据指针变量的类型来进行,因此int
型指针解应用后是整个int
变量的值,而不仅仅是那个指针指向的那一个字节处的值,这也是指针变量需要类型的原因之一。再者当涉及到*(p+1)
这种指针偏移
操作时编译器也会根据指针变量的类型,自动调整所需增加的地址(单位:字节
)。如果是int
型的指针变量,那么这里加1
,实际地址加4
。如果是char型的p,那么这里加一,地址就是加一。
int *p_num
、double *p_double
两个指针指向的数据类型长度不一样,但是指针(地址)的长度通常都是一样的。一般地址(指针)的长度为2个、4个字节,取决于计算机系统。int *pt = &num
,像这样被初始化的是指针pt
而不是它指向的值 *pt
。极其重要的一点是:在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间的应该是初始化指针之前的一个独立的步骤。
int* p_num; // 声明指针,但是并没有进行初始化
*p_num = 123; // 修改值,但是因为指针没有初始化,它并没有指向任何一个地址,因此会报错。
应该在赋值之前初始化数据地址
int num = 123;
int* p_num = #
*p_num = 123;
不能简单的将整数赋值给指针。
int * p_num;
p_num = 0xf5100000; // 不能直接将整数赋值给指针
p_num = (int *)0xf5100000; // 把整数强转成地址再赋值给指针
指针是实现 OOP
技术的重要部分。
变量,是在 编译阶段分配的、有名称的内存。
而指针只是为了可以通过名称直接访问内存提供了一个别名。
指针真正的用武之地在于,在运行阶段分配未命名的内存 以存储值。
在这种情况下,只能通过指针来访问内存,而不知道内存的确切位置 。
在运行阶段为一个int
值分配未命名的内存,并使用指针来访问这个值。
程序员要告诉 new
,需要为哪种数据类型分配内存。new
将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
int *pn = new int;
如此 pn
就是指向新内存的指针,而新内存里存储着一个 int
变量,但是这个变量没有名称,称为数据对象更为合适,且只能通过指针来访问。
分配数组内存
int *par = new int[10]; // new 操作符返回第一个元素的内存地址赋值给指针
注意:
int
数组的长度无法获取,一般在初始化的时候记录下来。
内存被耗尽:
- 计算机可能会由于没有足够的内存而无法满足
new
的请求。在这种情况下,new将返回O。- 在C++中,值为
0
的指针被称为空值指针null pointer
。C++ 确保空值指针不会指向有效的数据,因此它常被用来表
示操作符或函数失效,如果成功,它们将返回一个有用的指针。- 如果无法分配内存,new除返回空值指针外,还可能引发
bad alloc
异常。- 对于数组而言,
new
操作符返回的是第一个元素的内存地址
创建动态结构
#include
#include
struct inflatable
{
char name[20];
float volume;
double price;
};
int main() {
using namespace std;
inflatable* ps = new inflatable; // 创建一个未命名的 inflatable 结构
cout << "input name: ";
cin.get(ps->name, 20);
cout << "input volume: ";
cin >> (*ps).volume;
cout << "input price: ";
cin >> ps->price;
cout << "name = " << (*ps).name << endl;
cout << "volume = " << ps->volume << endl;
cout << "price = " << ps->price << endl;
return 0;
}
—>
成员操作符:提取指针指向的数据结构中的成员变量或函数,(*ps).name == ps->name
一般在 C++ 程序中,使用完内存需要手动释放内存。
int *ps = new int; // new 分配一块容纳 int 类型数据的内存,把地址赋给 ps 指针
// 使用指针
delete ps: // 释放掉名为 ps 的指针指向的内存
delete ps: // 释放已经释放过的内存块,将可能发生未知后果,是不安全的行为。
int *par = new int[10];
delete [] par; // 释放数组指针
注意:
- delete ps 释放掉指针指向的内存,但 并没有删除 ps 这个指针,ps 还能重新赋值其他地址继续使用
- 不要尝试释放已经释放的内存块,很可能会发生未知的后果,所以一般不要使用两个指针指向同一块内存,容易出现重复释放的问题。但是对空指针使用 delete 释放是安全的。
- 不要用 delete 去释放不是 new 分配的内存。
- 如果使用 new [] 为数组分配内存,则应使用 delete [] 释放内存。
- 如果使用 new [] 为实体分配内存,则应使用 delete 释放内存。
在 C++ 编程中,一定要配对地使用new和delete,否则将发生 内存泄漏(memory leak)。
内存泄漏是指内存本应可被分配使用空间少了一块,通常是因为在 new 了一个数据对象,分配了一块地址,赋给了一个指针后,当指针重新指向另外一个地址之前,没有 delete 释放这个内存,这个内存就处于无引用状态,但 C++ 又不会主动释放没有被引用的内存 ,导致这块内存一直被占用着,于是本来能用的存储就无端少了一块,缩水了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。
new 和 delete 操作字符串 范例
#include
#include
#pragma warning(disable:4996)
// #define _CRT_SECURE_NO_WARNINGS
using namespace std;
char* getName(void); // getName() 函数
int main() {
char* name;
name = getName();
cout << "name 内存 = " << (int*)name << endl; // (int*)name 强转指针类型,cout 对于 int* 会输出地址
delete[] name;
// 重用指针
name = getName();
cout << "name 内存 = " << (int*)name << endl;
delete[] name;
return 0;
}
char* getName(void)
{
char temp[80]; // 创建长度为 80 的临时字符数组
cout << "数组名:";
cin >> temp; // // 给 temp 赋值
char* pn = new char[strlen(temp) + 1]; // new 创建合适长度的新字符数组并把地址赋值给指针
strcpy(pn, temp); // 把临时数组的内容复制到新数组
return pn; // 返回新数组的指针
}
说明:程序运行出现以下报错,说明你用的是微软的 cl.exe 编译器,它认为 strcpy 函数不安全,好像其他编译器不会。
错误 C4996 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details
- 1
解决:头文件引入
#define _CRT_SECURE_NO_WARNINGS
或#pragma warning(disable:4996)
或用strcpy_s
函数代替strcpy
函数
指针和数组基本等价的原因在于指针算术(pointer arithmetic)和C+内部处理数组的方式。将整数变量加1后,其值将增加1:但将指针变量加1后,增加的量等于它指向的类型的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8;将指向shot的指针加1后,如果系统对shot使用2个字节存储,则指针值将增加2。
根据分配内存的方式不同,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。
在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable)。
这意味着它们在所属的函数被调用时 自动产生,在该函数结束时消亡。
例如,程序中的 temp 数组仅当 getName()函数活动时存在。当程序控制权回到 main() 时,temp 使用的内存将 自动被释放。如果 getName() 返回 temp 的地址,则 main() 中的 name 指针指向的内存(即 temp 的地址所在的内存)很大几率会被覆盖掉重新使用。这就是在 getName() 中使用 new 创建匿名数组分配内存,再返回指针的原因,其不会被自动释放。实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。到目前为止,我们使用的所有代码块都是整个函数。函数内也可以有代码块。如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行该代码块中的代码时存在。
静态存储是 整个程序执行期间都存在 的存储方式。使变量成为静态的方式有两种:
static double fee 56.50;
new
和delete
操作符提供了一种比自动变量和静态变量更灵活的方法。
它们管理了一个内存池,这在C++
中被称为自由存储空间(free store
)。内存池同用于静态变量和自动变量的内存是分开的。
上面的程序表明,new
和delete
允许在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期就 不完全受到程序或函数的生存时间的控制了。与使用常规变量相比,使用new
和delete
使程序员对程序如何使用内存有更大的控制权。
如果使用new
操作符在自由存储空间(堆)上创建变量后,后续没有调用delete
释放数据内存,则当包含指针的内存由于作用域规则和对象生命周期的原因被释放,在自由存储空间上动态分配的变量或结构也将继续存在。这将导致无法这些存在的变量或结构,因为指向这些内存的指针已经被释放掉了或者失效了,这将导致 内存泄漏 <了解详细> 。被泄漏的内存将在程序的整个生命周期内都不可使用。这些内存被分配出去,但无法收回。极端情况(不过不常见)是,内存泄漏可能会非常严重,以致于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩渍。
另外,这种泄漏还会给一些操作系统或在相同的内存空间中运行的应用程序带来负面影响,导致它们也相应崩溃。即使是最好的程序员和软件公司,也可能导致内存泄漏。
要避免内存泄漏,最好是养成这样一种习惯,即同时使用new
和delete
操作符,在自由存储空间上动态分配内存,随后便释放它。
注意:
指针是功能最强大的C++工具之一,但也最危险,因为它们允许执行对计算机不友好的操作,如使用未经初始化的指针来访问内存或者试图释放同一个内存块两次。另外,在通过实践习惯指针表示法和指针概念之前,指针是容易引起迷惑的。
数组、结构和指针是C++的3种 复合类型。
数组:
可以在一个数据对象中存储多个同种类型的值。通过使用索引或下标,可以访问数组中各个元素。
结构:
可以将多个不同类型的值存储在同一个数据对象中,可以使用成员关系操作符(.)来访问其中的成员。使用结构的第一步是创建结构模板,它定义结构存储了哪些成员。模板的名称将成为新类型的标识符,然后就可以声明这种类型的结构变量。
共用体:
可以存储一个值,但是这个值可以是不同的类型,成员名指出了使用的模式。指针是被设计用来存储地址的变量的。我们说,指针指向它存储的地址。指针声明指出了指针指向的对象的类型。对指针应用解除引用操作符,将得到指针指向的位置中的值。
字符串:
是以空字符为结尾的一系列字符。字符串可用引号括起的字符串常量表示,其中隐式包含了结尾的空字符
。可以将字符串存储在char数组
中,可以用被初始化为指向字符串的char指针
表示字符串。函数strlen()
返回字符串的长度,其中不包括空字符。函数strcpy()
将字符串从一个位置复制到另个位置。在使用这些函数时,应当包含头文件cstring
或string.h
。头文件string
支持的C++ string
类提供了另一种对用户更为友好的字符串处理方法。具体地说,string
对象将根据要存储的字符串自动调整其大小,用户可以使用赋值操作符来复制字符串。new
操作符允许在程序运行时为数据对象请求内存。该操作符返回获得内存的地址,可以将这个地址赋给一个指针,程序将只能使用该指针来访问这块内存。如果数据对象是简单变量,则可以使用解除引用操作符*
来获得其值。如果数据对象是数组,则可以像使用数组名那样使用指针来访问元素。如果数据对象是结构,则可以用指针解除引用操作符->
来访问其成员。
指针和数组紧密相关。
如果ar
是数组名,则表达式ar[i]
被解释为*(ar+i)
,其中数组名被解释为数组第一个元素的地址。这样,数组名的作用和指针相同。反过来,可以使用数组表示法,通过指针名来访问new
分配的数组中的元素。操作符new
和delete
,允许显式控制何时给数据对象分配内存,何时将内存归还给内存池。自动变量是在函数中声明的变量,而静态变量是在函数外部或者使用关键字static
声明的变量,这两种变量都不太灵活。自动变量在程序执行到其所属的代码块(通常是函数定义)时产生,在离开该代码块时终止。静态变量在整个程序周期内都存在。