进制与指定进制输出
常用进制
C++使用前一(两)位来标识数字常量的基数:
- 十进制:第一位为1 ~ 9,如93就是以10为基数的十进制数。
- 八进制:第一位是0,第二位为1~7,如042的基数是8,是个八进制数。
- 十六进制:前两位为0x或0X,如 0x42 就是基数为16的十六进制数,相当于 10 进制数66。对于十六进制数,字符a ~ f 和 A ~ F表示了十六进制位,对应10 ~ 15。如 0xF 为15,0x45 为 69(4个16加5个1)。
输出指定的进制格式
使用 cout 输出时可以通过控制符 dec(十进制)、otc(八进制)、hex(十六进制) 指定输出格式
#include<iostream>
#include<string>
int main() {
using namespace std;
cout << oct;
cout << 16 << endl;
return 0;
}
如果想要以二进制输出,可以用 bitset< size >(int) 函数
#include<iostream>
#include<bitset>
int main() {
cout << "54的二进制数 = " << bitset<8>(54) << endl;
return 0;
}
结构与位字段
位字段
位字段用于指定使用的位数,常用于整形和枚举。平常编程一般用不着,多用于低级编程。
#include<iostream>
#include<string>
#include<bitset>
struct structureOne
{
unsigned int num : 4;
unsigned int: 4;
bool flag1 : 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;
cout << "one.num = " << one.num << endl;
return 0;
}
说明: 因为数据结构中指定了 num 的位数为4,原二进制数 0011 0110 从低位取起,取得的二进制数为 0110,所以输出十进制数 one.num = 6
共同体 union
共同体和结构差不多,也可以构建一个多变量数据结构,但特点是里面的变量共享内存,即只有以变量类型中最长的类型为长度的那一块内存。基于此特点,共同体只能同时存储一个变量(保证一个变量的准确)。 如: ????共同体内有 int a 和 double b 两个变量,先给 a 赋值,后在给 b 赋值时,则 a 的值可能被覆盖。 ????反之如果先给 b 赋值,在给 a 赋值,b 的值可能不会被覆盖。 具体要看数据类型的长度和赋值的大小而定,但总的来说能保证的就其中一个变量,这也是共同体设计出来的意义。
#include<iostream>
#include<string>
using namespace std;
union unionOne
{
int int_val;
double double_val;
};
int main() {
unionOne one;
one.double_val = 87.54;
cout << "double_val = " << one.double_val << endl;
cout << "intVal = " << one.int_val << endl;
one.int_val = 90;
cout << "double_val = " << one.double_val << endl;
cout << "int_val = " << one.int_val << endl;
one.double_val = 45.65;
cout << "double_val = " << one.double_val << endl;
cout << "int_val = " << one.int_val << endl;
return 0;
}
在 double_val 有值的情况下,给 int_val 赋值后,double_val 的值没有被覆盖。 后面再给 double_val 赋值后,int_val 的值被覆盖了。 多次测试后得出结论: 给小类型变量赋值大类型变量不一定会被覆盖,但给最大类型变量赋值小类型变量一定会被覆盖。
共用体的用途: ????当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如,假设管 理一个小商品目录,其中有一些商品的 id 为整型,而另一些的 id 为字符串。在这种情况下,可以这样做:
struct goods{
char brand[20]:
int type:
union id{
long id_num;
char id_char[20];
}id_val;
};
goods goodOne;
if(goodOne.type==1)
cin > goodOne.id_val.id_num;
else
cin > goodOne.id_val.id_char:
枚举
C++的enum 工具提供了另一种创建符号常量的方式,这种方式可以代替const。.
#include<iostream>
#include<string>
using namespace std;
int main() {
enum color{red,orange,yellow,green};
cout << "red = " << red << endl;
cout << "orange = " << orange << endl;
cout << "yellow = " << yellow << endl;
cout << "green = " << green << endl;
int num = yellow;
cout << "num = " << green << endl;
color colorOne;
colorOne = orange;
cout << "colorOne = " << colorOne << endl;
colorOne = color(2);
cout << "colorOne = " << colorOne << endl;
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不是枚举值,但它位于枚举定义的取值范围内。
取值范围的定义如下。 ????首先,要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。 ????例如,前面定义的 bigstep 的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0。否则,采用与寻找上限方式相同的方式,但加上负号。 ????例如,如果最小的枚举量为-6,而比它小的、最大的2的幂是-8(加上负号),因此下限为-7。选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用1个字节或更少的空间。而对于包含long类型值的枚举,则使用4个字节。
指针?????
指针是一个变量,其存储的是值得地址,而不是值本身。
找到一个变量的地址
如何找到一个常规变量的地址?只需要对变量应用地址操作符 ’&‘ 即可。
#include<iostream>
#include<string>
int main() {
using namespace std;
int num = 5;
string str_one = "嘻嘻嘻";
cout << "num 地址:" << &num <<endl;
cout << "str_one 地址:" << &str_one << endl;
return 0;
}
*操作符
* 操作符称为间接值操作符或解除引用操作符,编译器会智能区分操作符用作 解除引用 还是 乘法。 对于一个指针变量来说,指针名表示 地址,指针的间接值表示存储在该地址的 值。
#include<iostream>
#include<string>
int main() {
using namespace std;
int num1 = 5;
string str_one = "嘻嘻嘻";
cout << "num 地址:" << &num1 <<endl;
cout << "str_one 地址:" << &str_one << endl;
int num = 6;
int *p_num;
p_num = #
cout << "num = " << num << endl;
cout << "*p_num = " << *p_num << endl;
cout << "&num = " << &num << endl;
cout << "p_num = " << p_num << endl;
*p_num = *p_num + 1;
cout << "after *p_num + 1, num = " << num << endl;
return 0;
}
int 变量 num 和指针变量 p_num 只不过是同一枚硬币的两面。 变量 num 表示值,使用 & 操作符来获得地址。 而变量 p_num 表示地址,并使用 * 操作符来获得值。 由于 p_num 指向num ,因此*p_num 和 num 完全等价。 可以像使用int变量那样使用*p_num 。甚至可以将值赋给*p_num ,这样做将修改指向的值,即 num 。
声明和初始化指针
- 指针是一种复合类型变量。
- 指针声明必须指定指针指向的数据的类型。如
int *p_num 、double *p_double 、char* p_char 。类型* 指针名 和类型 *指针名 都是合法的。 - 虽然
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;
使用 new 操作符分配内存
指针是实现 OOP 技术的重要部分。 变量,是在 编译阶段分配的、有名称的内存。 而指针只是为了可以通过名称直接访问内存提供了一个别名。 指针真正的用武之地在于,在运行阶段分配未命名的内存 以存储值。 在这种情况下,只能通过指针来访问内存,而不知道内存的确切位置 。
在运行阶段为一个int 值分配未命名的内存,并使用指针来访问这个值。 程序员要告诉 new ,需要为哪种数据类型分配内存。new 将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
int *pn = new int;
如此 pn 就是指向新内存的指针,而新内存里存储着一个 int 变量,但是这个变量没有名称,称为数据对象更为合适,且只能通过指针来访问。
分配数组内存
int *par = new int[10];
注意:int 数组的长度无法获取,一般在初始化的时候记录下来。
内存被耗尽:
- 计算机可能会由于没有足够的内存而无法满足
new 的请求。在这种情况下,new将返回O。 - 在C++中,值为
0 的指针被称为空值指针null pointer 。C++ 确保空值指针不会指向有效的数据,因此它常被用来表 示操作符或函数失效,如果成功,它们将返回一个有用的指针。 - 如果无法分配内存,new除返回空值指针外,还可能引发
bad alloc 异常。 - 对于数组而言,
new 操作符返回的是第一个元素的内存地址
创建动态结构
#include<iostream>
#include<string>
struct inflatable
{
char name[20];
float volume;
double price;
};
int main() {
using namespace std;
inflatable* ps = new 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
使用 delete 释放内存
一般在 C++ 程序中,使用完内存需要手动释放内存。
int *ps = new int;
delete 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<iostream>
#include<cstring>
#pragma warning(disable:4996)
using namespace std;
char* getName(void);
int main() {
char* name;
name = getName();
cout << "name 内存 = " << (int*)name << endl;
delete[] name;
name = getName();
cout << "name 内存 = " << (int*)name << endl;
delete[] name;
return 0;
}
char* getName(void)
{
char temp[80];
cout << "数组名:";
cin >> temp;
char* pn = new char[strlen(temp) + 1];
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
解决:头文件引入 #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:
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 声明的变量,这两种变量都不太灵活。自动变量在程序执行到其所属的代码块(通常是函数定义)时产生,在离开该代码块时终止。静态变量在整个程序周期内都存在。
|