C++ Primer Plus学习笔记
C++ Primer Plus
第1章 预备知识
区分扩展名
-
C++程序使用.C或者.cpp作为扩展名,C程序使用.c作为扩展名
-
对于某些UINX系统,也可使用扩展名cc和cxx
-
DOS不区分大小写
源代码文件的扩展名
C++实现 | 源代码文件的扩展名 |
---|---|
UNIX | C、cc、cxx、c |
GNU C++ | C、cc、cxx、cpp、c++ |
Digital Mars | cpp、cxx |
Borland C++ | cpp |
Watcom | cpp |
Microsoft Visual C++ | cpp、cxx、cc |
Freestyle Code Warrior | cp、 cpp、 cc、cxx、c++ |
第2章 开始学习C++
1、C++语句
- 语句是要执行的操作
- C与C++使用终止符(terminator)而不是分隔符
- 终止符是一个分号(;)符,他是语句结束的标志,是语句的组成部分
- 声明语句:定义函数中使用的变量的名称和类型。
- 赋值语句:使用赋值运算符(=)给变量赋值。
- 消息语句:将消息发送给对象,激发某种行动。
- 函数调用:执行函数。被调用的函数执行完毕后,程序返回到函数调用语句后面的语句。
- 函数原型:声明函数的返回类型、函数接受的参数数量和类型。
- 返回语句:将一个值从被调用的函数那里返回到调用函数中。
2、C++注释
- C++注释以//打头,到行尾结束
- C++也能识别C的注释/**/
- 尽量使用C++风格注释
3、C++源代码风格
- 每条语句占一行
- 每个函数都有一个开始花括号和一个结束花括号,这两个花括号各占一行。
- 函数中的语句都相对于花括号进行缩进。
- 与函数名称相关的圆括号周围没有空白。
4、命名约定
有以下几种
- Myfunction( )
- myfunction( )
- myFunction( )
- my_function( )
- my_funct( )
选择取决于开发团体、使用的技术或库以及程序员个人的品位和喜好。
第3章 处理数据
1、变量名命名规则
- 在名称中只能使用字母字符、数字和下划线(_)。
- 名称的第一个字符不能是数字。
- 区分大写字符与小写字符。不能将C++关键字用作名称。
- 以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
- C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
2、变量长度
-
bool,
-
short(short为short int的简称)至少16位(bit),两个字节(bytes),表示范围-3276832767*,*(*即*-2^152^15-1);
-
w_char(宽字符类型),可以表示扩展字符集,是一种整数类型,长度不确定
-
char,1byte=8bit;表示范围不确定,默认情况下无符号,是否有符号有C++实现决定
- signed char,表示范围-128~127
char fodo;//可能有符号,可能没符号 unsigned char bar;//无符号 signed char snark;//有符号
-
C++11新增类型
- char16_t:无符号,长16位
- char32_t:无符号,长32位
- C++11使用前缀u表示u表示char16_t字符串常量和字符常量,如:u'C'和u"be good";前缀U表示char32_t字符串常量和字符常量,如:U'C'和U"be good"
- 与wchar_t一样,char16_t和 char32_t也都有底层类型——一种内置的整型,但底层类型可能随系统而已。
-
int被设置为对目标计算机而言最为“自然”的长度,自然长度(natural size)指的是计算机处理起来效率最高的长度。int至少与short一样长,4byte=32bit,所能表示范围:-21474836482147483647*;*(*即*-2^312^31-1);
-
long(long为long int的简称)至少32位,且至少与int一样长,4byte=32bit,所能表示范围:-21474836482147483647*;*(*即*-2^312^31-1);
-
long long 至少64位,且至少与long一样长。
-
float,至少32位,指数范围至少是-37到37
-
double,至少48位,且不少于float,指数范围至少是-37到37
-
long double,位80、96或128位,指数范围至少是-37到37
可以从头文件cfloat或float.h中找到系统的限制。
注:wchar_t类型是一种整数类型,它有足够的空间,可以表示系统使用的最大扩展字符集。这种类型与另一种整型(底层(underlying)类型)的长度和符号属性相同。对底层类型的选择取决于实现,因此在一个系统中,它可能是unsigned short,而在另一个系统中,则可能是int。cin和 cout将输入和输出看作是char流,因此不适于用来处理 wchar_t类型。iostream头文件的最新版本提供了作用相似的工具——wcin和 wcout,可用于处理 wchar_t流。另外,可以通过加上前缀L来指示宽字符常量和宽字符串。
3、运算符sizeof
sizeof运算符返回类型或数据对象的长度(单位为字节),可对类型名或变量名使用sizeof运算符。
- 对类型名(如 int)使用sizeof运算符时,应将名称放在括号内;
- 对变量名(如n_short)使用该运算符,括号是可选的。
cout << "int is" << sizeof(int) << " bytes.\n";
cout << "short is " << sizeof n_short << " bytes.\n";
4、头文件limits
头文件climits 定义了符号常量来表示类型的限制。INT_MAX表示类型 int能够存储的最大值,对于Windows 7系统,为2147483 647。编译器厂商提供了climits文件,该文件指出了其编译器中的值。例如,在使用16位int 的老系统中,climits文件将INT_MAX定义为32767。下表对该文件中定义的符号常量进行了总结,其中的一些符号常量与还没有介绍过的类型相关。
符号常量 | 表示 |
---|---|
CHAR_BIT | char的位数 |
CHAR_MAX | char的最大值 |
CHAR_MIN | char的最小值 |
SCHAR_MAX | signed char的最大值 |
SCHAR_MIN | signed char的最小值 |
UCHAR_MAX | unsigned char的最大值 |
SHRT_MAX | short的最大值 |
SHRT_MIN | short的最小值 |
USHRT_MAX | unsigned short的最大值 |
INT_MAX | int的最大值 |
INT_MIN | int的最小值 |
UNIT_MAX | unsigned int的最大值 |
LONG_MAX | long的最大值 |
LONG_MIN | long的最小值 |
ULONG_MAX | unsigned long的最大值 |
LLONG_MAX | long long的最大值 |
LLONG_MIN | long long 的最小值 |
ULLONG_MAX | unsigned long long的最大值 |
5、C++11初始化方式
C++11使得大括号初始化器用于任何类型(可以使用等号,也可以不使用),这是一种通用的初始化语法。
int hamburgers = {24};//初始化方式一
int emus{7};//初始化方式二
int rheas = 12;//初始化方式三
6、无符号类型
-
unsigned char:所占内存大小:1byte=8bit;所能表示范围:0255*;*(02^8-1)
-
unsigned(unsigned int):所占内存大小:4byte=32bit; 能表示范围:04294967295*;*(*即*02^32-1)
-
unsigned long: 所占内存大小:4byte=32bit;所能表示范围:04294967295*;*(*即*02^32-1)
7、整型字面值
-
十进制
int chest = 10;
-
八进制:前缀为0
int inseam = 042;
-
十六进制:前缀为0x或0X
int waist = 0x42;
-
如果要以十六进制或八进制方式显示值,则可以使用cout的一些特殊特性。控制字符dec、oct和hex分别用于指示cout以十进制、八进制和十六进制格式显示整数。
cout << dec; //表示cout显示十进制数 ...... cout << oct; //表示cout显示八进制数 ...... cout << hex; //表示cout显示十六进制数 ......
8、确定常量的类型
- long:后缀为L或l,如:100L
- unsigned int:后缀为U或u,如:100U
- unsigned long:后缀为UL(LU,lu,ul),如100ul
- long long:后缀为LL或ll,如100LL
- unsigned long long:后缀为ULL(LLU,llu,ull),如100ull
接下来考察长度。在C++中,对十进制整数采用的规则,与十六进制和八进制稍微有些不同。对于不带后缀的十进制整数,将使用下面几种类型中能够存储该数的最小类型来表示: int、long或long long。在 int 为 16位、long 为 32位的计算机系统上,20000被表示为 int 类型,40000被表示为long类型,3000000000被表示为long long类型。对于不带后缀的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、unsigned int long、unsigned long、long long或unsigned long long.在将40000表示为long 的计算机系统中,十六进制数Ox9C40 ( 40000)将被表示为unsigned int。这是因为十六进制常用来表示内存地址,而内存地址是没有符号的,因此,usigned int 比 long更适合用来表示16位的地址。
9、C++转义字符
字符名称 | ASCII符号 | C++代码 | 十进制ASCII码 | 十六进制ASCII码 |
---|---|---|---|---|
换行符 | NL (LF) | \n | 10 | 0xA |
水平制表符 | HT | \t | 9 | 0x9 |
垂直制表符 | VT | \v | 11 | 0xB |
退格 | BS | \b | 8 | 0x8 |
回车 | CR | \r | 13 | 0xD |
振铃 | BEL | \a | 7 | 0x7 |
反斜杠 | \ | \\ | 92 | 0x5C |
问号 | ? | ? | 63 | 0x3F |
单引号 | ' | \' | 39 | 0x27 |
双引号 | " | \" | 34 | 0x22 |
10、const限定符
创建常量的通用格式
const type name = value;//在C++中尽量用const取代#define
常量指针:const修饰指针,指针指向可以变,指针指向的值不可以变
const int * p = &a;
指针常量:const修饰的是常量,指针指向不可以变,指针指向的值可以变
int * const p = &a;
const即修饰指针又修饰常量:指针指向不可以变,指针指向的值也不可以变
const int * const p = &a;
11、浮点数的表示
12.34//float
2.52e+8//E的表示法,+可省略,E大小写皆可
-18.32e-13//E的表示法,-不可省略
1)定点表示法
ostream方法 setf(),迫使输出使用定点表示法,以便更好地了解精度,它防止程序把较大的值切换为E表示法,并使程序显示到小数点后6位。参数 ios_base:.fixed 和ios_base::floatfield是通过包含iostream来提供的常量。
#include
int main ()
{
using namespace std;
cout.setf(ios_base::fixed, ios_base::floatfield);
//迫使cout输出使用定点表示法,并使程序显示到小数点后6位
......
return 0 ;
}
2)浮点常量
- float:后缀为f或F,如:1.2f
- double:默认浮点数都是double,如:8,24和2.4E8
- long double:后缀为l或L,如1.234L
12、除法分支
-
整数/整数=整数(小数部分省略)
-
double/double = double
-
double/int = double
-
int/double = double
-
double/double = double
-
float/float = float
13、求模运算
求模符号:%
规则:只能为整数,x(x>=n)与n求模,求模后取值为小于n的整数,如:
//与二求模
for(int i=1;i<=10;i++){
//TODO
cout << i << "%2 = " << i%2 << " ";//与2求模,求模后取值0,1,为小于2的整数
}
cout << endl;
//与三求模
for(int i=1;i<=10;i++){
//TODO
cout << i << "%3 = " << i%3 << " ";//与3求模,求模后取值0,1,2,为小于3的整数
}
cout << endl;
14、类型转换
转换规则
C++11对这个校验表稍做了修改,下面是C++11版本的校验表,编译器将依次查阅该列表:
- 如果有一个操作数的类型是long double,则将另一个操作数转换为long double。
- 否则,如果有一个操作数的类型是double,则将另一个操作数转换为double。
- 否则,如果有一个操作数的类型是float,则将另一个操作数转换为float。
- 否则,说明操作数都是整型,因此执行整型提升。
- 在这种情况下,如果两个操作数都是有符号或无符号的,且其中一个操作数的级别比另一个低,则转换为级别高的类型。
- 如果一个操作数为有符号的,另一个操作数为无符号的,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型。
- 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号操作数转换为有符号操作数所属的类型。
- 否则,将两个操作数都转换为有符号类型的无符号版本。
有符号整型按级别从高到低依次为long long、long、int、short和 signed char。无符号整型的排列顺序与有符号整型相同。类型char、signed char和unsigned char的级别相同。类型bool 的级别最低。wchar_t、char16_t和char32_t 的级别与其底层类型相同。
强制类型转换
(typeName) value;//方法一,C语言版
typeName (value);//方法二,C++版,新格式的想法是,要让强制类型转换就像是函数调用一样
//C++还引入了4个强制类型转换运算符,对它们的使用要求更为严格
statistic_cast (value)
15、C++11中的auto声明
C++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型。为此,它重新定义了auto的含义。auto是一个C语言关键字,但很少使用。在初始化声明中,如果使用关键字auto,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同:
auto n = 100;//n为int
auto x = 1.5;//x为double
auto y = 1.3e12L;//y为long double
显式地声明类型时,将变量初始化0(而不是0.0)不会导致任何问题,但采用自动类型推断时,这却会导致问题。
第4章 复合类型
1、数组
数组之所以被称为复合类型,是因为它是使用其他类型来创建的(C语言使用术语“派生类型”,但由于C++对类关系使用术语“派生”,所以它必须创建一个新术语)。
//声明数组
typeName arrayName[arraySize];
表达式arraySize 指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof ( int)),即其中所有的值在编译时都是已知的。具体地说,arraySize不能是变量,变量的值是在程序运行时设置的。然而,本章稍后将介绍如何使用new运算符来避开这种限制。
float loans[20];
loans 的类型不是“数组”,而是“float数组”。这强调了loans数组是使用float类型创建的。
2、字符串
-
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。第一种来自C语言,常被称为C-风格字符串(C-style string)。
-
C-风格字符串具有一种特殊的性质:以空字符(null character)结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。例如,请看下面两个声明:
char dog[8] = {'b', 'e', 'a', "u ', 'x', ' ', 'I','I'};//不是字符串 char cat[8] = {'f', 'a' , 't', 'e', 's', 's', 'a', '\0'};//是字符串
-
字符串常量(string constant)或字符串字面值(string literal)
-
在cat 数组示例中,将数组初始化为字符串的工作看上去冗长乏味——使用大量单引号,且必须记住加上空字符。不必担心,有一种更好的、将字符数组初始化为字符串的方法—只需使用一个用引号括起的字符串即可。
char bird[11] = "Mr. Cheeps"; char fish[] = "Bubbles";
-
C++输入工具通过键盘输入,将字符串读入到char数组中时,将自动加上结尾的空字符
-
1)拼接字符串
cout <<"1'd give my right ar"
"m to be a great violinist.\n" ;
注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。
2)strlen函数
strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。另外,strlen()只计算可见的字符,而不把空字符计算在内。
3)cin读取字符串
cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后, cin将该字符串放到数组中,并自动在结尾添加空字符。
4)每次读取一行字符串输入
-
面向行的输入:getline()
getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。要调用这种方法,可以使用cin.getline( )。该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。
-
面向行的输入:get()(推荐使用)
istream类有另一个名为 get( )的成员函数,该函数有几种变体。其中一种变体的工作方式与getline( )类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get并不再读取并丢弃换行符,而是将其留在输入队列中。
get()有另一种变体。使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符)因此可以用它来处理换行符,为读取下一行输入做好准备。
3、string类
在很多方面,使用string 对象的方式与使用字符数组相同。
- 可以使用C-风格字符串来初始化string对象。
- 可以使用cin来将键盘输入存储到string 对象中。
- 可以使用cout来显示string 对象。
- 可以使用数组表示法来访问存储在string对象中的字符。
C++11也允许将列表初始化用于C-风格字符串和 string对象:
char first_date[] ={"Le chapon Dodu"};
char second_date[] {"The Elegant Plate"};
string third_date = {"The Bread Bowl "};
string fourth_date {"Hank 's Fine Eats"};
string 类简化了字符串合并操作。可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string 对象的末尾。
string str3;
str3 = str1 + str2;
str1 += str2;
strcpy()与strcat()方法
在C++新增string类之前。对于C-风格字符串,使用C语言库中的函数来完成这些任务。头文件cstring (以前为string.h)提供了这些函数。例如,可以使用函数strcpy()将字符串复制到字符数组中,使用函数 strcat()将字符串附加到字符数组末尾:
strcpy(charr1, charr2);//复制charr2到charr1
strcat(charr1, charr2);//将charr2拼接到charr1后面
string 类具有自动调整大小的功能,从而能够避免这种问题发生,C函数库提供了与strcat()和 strcpy()类似的函数——strncat()和 strncpy(),它们接受指出目标数组最大允许长度的第三个参数,因此更为安全,但使用它们进一步增加了编写程序的复杂度。
size()类方法
函数strlen()是一个常规函数,它接受一个C-风格字符串作为参数,并返回该字符串包含的字符数。函数 size()的功能基本上与此相同。但size()是类方法,通过对象.size()调用。
原始(raw)字符串
在原始字符串中,字符表示的就是自己,例如,序列\n不表示换行符,而表示两个常规字符—斜杠和n,因此在屏幕上显示时,将显示这两个字符。原始字符串将"(和)"用作定界符,并使用前缀R来标识原始字符串:
cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n';
//上述代码将显示如下内容:
//Jim "King" Tutt uses "\n" instead of endl .
输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符。
如果要在原始字符串中包含)",使用R"+*(标识原始字符串的开头时,必须使用)+*"标识原始字符串的结尾。这使用"+*(和)+*"替代了默认定界符"(和)"。自定义定界符时,在默认定界符之间添加任意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。
cout << R"+*("( Who wouldn't?) ", she whispered.)+*" << endl ;
//" ( who wouldn't?) ", she whispered.
4、共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构可以同时存储int、long 和 double,共用体只能存储int、long或double。共用体的句法与结构相似,但含义不同。
union one4all
{
int int_val;
long long_val;
double double_val;
};
one4all pail;
pail.int_val = 15;
cout << pail.int_val;
pail. double_val = 1.38;
cout << pail.double_val;
pail 有时可以是int 变量,而有时又可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度。
匿名共用体(anonymous union)没有名称,其成员将成为位于相同地址处的变量。显然,每次只有一个成员是当前的成员:
struct widget{
char brand [20];
int type;
union
{
long id_num;
char id_char [20];
};
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
由于共用体是匿名的,因此id_num和 id_char被视为prize的两个成员,它们的地址相同,所以不需要中间标识符id_val。程序员负责确定当前哪个成员是活动的。
5、枚举
1)定义
使用enum的句法与使用结构相似。
enum spectrum {red,orange, yellow, green, blue,violet, indigo, ultraviolet
};
这条语句完成两项工作。
- 让 spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
- 将red、 orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫作枚举量( enumerator )。
在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。
可以用枚举名来声明这种类型的变量:
spectrum band;
在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量:
band = blue;//允许
band = 2000;//非法
因此,spectrum 变量受到限制,只有8个可能的值。如果试图将一个非法值赋给它,则有些编译器将出现编译器错误,而另一些则发出警告。为获得最大限度的可移植性,应将把非enum值赋给enum变量视为错误。
对于枚举,只定义了赋值运算符。具体地说,没有为枚举定义算术运算:
band = orange;//允许
++band;//不允许
band = orange + red;//不合理
...
枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型:
int color = blue;//允许
band = 3;//不允许,int不能转换为枚举型
color = 3 + red;//允许,枚举量为整型被提升为int型
...
2)设置枚举的值
可以使用赋值运算符来显式地设置枚举量的值,指定的值必须是整数。也可以只显式地定义其中一些枚举量的值。
enum bigstep{first, second = 100, third};
这里,first 在默认情况下为0。后面没有被初始化的枚举量的值将比其前面的枚举量大1。因此,third的值为101。
可以创建多个值相同的枚举量:
enum {zero, null = 0,one,numero_uno = 1};
//其中,zero和 null都为0,one和 umero_uno都为1。
3)枚举的取值范围
每个枚举都有取值范围(range),通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量,即使这个值不是枚举值。
enum bits{ one = 1, two = 2, four = 4, eight = B };
bits myflag;
myflag = bits(6);//允许,其中6不是枚举值,但它位于枚举定义的取值范围内。
上限:需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。例如,前面定义的 bigstep 的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127
下限:。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号。例如,如果最小的枚举量为-6,而比它小的、最大的2的幂是-8(加上负号),因此下限为-7。
6、指针
在C++中,type*是一种复合类型,是指向type的指针。
注:一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。
1)使用new来分配内存
在C语言中,可以用库函数malloc()来分配内存;在C++中仍然可以这样做,但C++还有更好的方法—new运算符。
typeName * pointer_name = new typeName;
//在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
int * pn = new int;
//在下面这种情况下,可以通过名称higgens 来访问该int,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题: pn指向的内存没有名称,如何称呼它呢﹖我们说pn 指向一个数据对象,这里的“对象”不是“面向对象编程”中的对象,而是一样“东西”。术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。因此,变量也是数据对象,但pn 指向的内存不是变量。乍一看,处理数据对象的指针方法可能不太好用,但它使程序在管理内存方面有更大的控制权。
int higgens;
int * pt = &higgens;
对于指针,需要指出的另一点是, new分配的内存块通常与常规变量声明分配的内存块不同.变量nights和pd的值都存储在被称为栈( stack)的内存区域中,而new从被称为堆(heap)或自由存储区( free store)的内存区域分配内存。
2)使用delete释放内存
使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的);只能用delete来释放使用new分配的内存。对空指针使用delete是安全的。
int * ps = new int;
...
delete ps;
3)使用new创建动态数组
int * psome = new int [10];//要创建一个包含10个int元素的数组
delete [] psome;//方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。请注意delete和指针之间的方括号。
总之,使用new和 delete时,应遵守以下规则:
- 不要使用delete来释放不是new 分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放。
- 如果使用new [ ]为一个实体分配内存,则应使用delete(没有方括号)来释放。
- 对空指针应用delete是安全的。
7、数组的替代品
1)模板类vector
模板类vector类似于string类,也是一种动态数组。可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和 delete来管理内存,但这种工作是自动完成的。
-
首先,要使用vector对象,必须包含头文件 vector。
-
其次,vector包含在名称空间std 中,因此可使用using 编译指令、using声明或std::vector。
-
第三,模板使用不同的语法来指出它存储的数据类型。
-
第四,vector类使用不同的语法来指定元素数。下面是一些示例:
#include
... using namespace std; vector vi; int n; cin >> n; vector vd(n); 其中,vi是一个vector
对象,vd是一个vector 对象。由于vector对象在您插入或添加值时自动调整长度,因此可以将vi的初始长度设置为零。但要调整长度,需要使用vector包中的各种方法。
一般而言,下面的声明创建一个名为vt的vector对象,它可存储n_elem个类型为typeName的元素:vector
vt(n_elem) ; 其中参数n_elem可以是整型常量,也可以是整型变量。
2)模板类array(C++11)
vector类的功能比数组强大,但付出的代价是效率稍低。如果您需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。有鉴于此,C++11新增了模板类array,它也位于名称空间std中。与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。要创建array对象,需要包含头文件array。array对象的创建语法与vector稍有不同:
#include
...
using namespace std;
array ai;
array ad = {1.2, 2.1, 3.43, 4.3};
推而广之,下面的声明创建一个名为arr 的array对象,它包含n_elem个类型为typename的元素;
array arr;
与创建vector对象不同的是,n_elem不能是变量。
在C++11中,可将列表初始化用于vector和array对象,但在C++98中,不能对vector对象这样做。
3)比较数组、vector对象和array对象
- 首先,无论是数组、vector对象还是array对象,都可使用标准数组表示法来访问各个元素。
- 其次,从地址可知,array对象和数组存储在相同的内存区域(即栈)中,而 vector对象存储在另一个区域(自由存储区或堆)中。
- 第三,注意到可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。
a2 [-2] = .5;//依然被允许
a3 [200] = 1.4 ;
可以选择是使用成员函数 at()。就像可以使用cin对象的成员函数getline()一样,您也可以使用vector和 array对象的成员函数at():
a2.at(1) = 2.3;//将2.3分配给a2[1]
中括号表示法和成员函数at()的差别在于,使用at()时,将在运行期间捕获非法索引,而程序默认将中断。这种额外检查的代价是运行时间更长,这就是C++让允许您使用任何一种表示法的原因所在。另外,这些类还让您能够降低意外超界错误的概率。例如,它们包含成员函数 begin()和 end(),让您能够确定边界,以免无意间超界。
第5章 循环和关系表达式
1、for循环
for (initialization; test-expression; update-expression)
body
循环只执行一次初始化。通常,程序使用该表达式将变量设置为起始值,然后用该变量计算循环周期。
test-expression(测试表达式)决定循环体是否被执行。通常,这个表达式是关系表达式,即对两个值进行比较。实际上,C++并没有将test-expression 的值限制为只能为真或假。可以使用任意表达式,C++将把结果强制转换为bool类型。因此,值为0的表达式将被转换为bool值false,导致循环结束。如果表达式的值为非零,则被强制转换为bool值 true,循环将继续进行。
C++常用的方式是,在for和括号之间加上一个空格,而省略函数名与括号之间的空格。如:
for (i = 6; i < 10; i++)
smart_function(i);
2、strcmp()函数
可以使用strcmp()来测试C-风格字符串是否相等(排列顺序)。如果 strl和str2相等,则下面的表达式为true:
strcmp(str1,str2) == 0;
3、while循环
while (test-condition)
body
在无法预先知道循环将执行的次数时,程序员常使用while循环。
在设计循环时,请记住下面几条指导原则:
-
指定循环终止的条件。
-
在首次测试之前初始化条件。
-
在条件被再次测试之前更新条件。
延时循环,clock()函数
ANSI C和C++库中有一个函数有助于完成延时工作。这个函数名为clock( ),返回程序开始执行后所用的系统时间。这有两个复杂的问题:首先,clock( )返回时间的单位不一定是秒;其次,该函数的返回类型在某些系统上可能是 long,在另一些系统上可能是unsigned long 或其他类型。但头文件ctime(较早的实现中为 time.h)提供了这些问题的解决方案。首先,它定义了一个符号常量——CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得到秒数。或者将秒数乘以CLOCK_PER_SEC,可以得到以系统时间单位为单位的时间。其次,ctime将clock_t 作为clock()返回类型的别名,这意味着可以将变量声明为clock _t类型,编译器将把它转换为 long、unsigned int或适合系统的其他类型。
类型别名
C++为类型建立别名的方式有两种。
-
一种是使用预处理器:#define BYTE char
这样,预处理器将在编译程序时用char替换所有的BYTE,从而使BYTE成为char 的别名。 -
第二种方法是使用C++(和C)的关键字typedef来创建别名。例如,要将byte作为char的别名,可以这样做:
typedef char byte;下面是通用格式:
typedef typeName aliasName;
它能够处理更复杂的类型别名,这使得与使用#define相比,使用typedef是一种更佳的选择——有时候,这也是唯一的选择。注意,typedef 不会创建新类型,而只是为已有的类型建立一个新名称。如果将word作为int的别名,则cout 将把word类型的值视为int类型。
4、do while循环
do
body
while(test-expression);
5、基于范围的for循环
C++11新增了一种循环:基于范围(range-based)的 for循环。这简化了一种常见的循环任务:对数组(或容器类,如vector和 array)的每个元素执行相同的操作,如下例所示:
double prices[5] = {4.99,10.99,6.87,7.99,8.49};
for (double x : prices)
cout << x << std::endl;
其中,x最初表示数组prices 的第一个元素。显示第一个元素后,不断执行循环,而x依次表示数组的其他元素。因此,上述代码显示全部5个元素,每个元素占据一行。总之,该循环显示数组中的每个值。
要修改数组的元素,需要使用不同的循环变量语法:
for (double &x : prices)
x = x * 0.80;
符号&表明x是一个引用变量,这种声明让接下来的代码能够修改数组的内容,而第一种语法不能。
还可结合使用基于范围的 for循环和初始化列表:
for (int x : {3,5, 2, 8, 6})
cout c< x << " ";
cout c< '\n';
6、循环和文本输入
知道循环的工作原理后,来看一看循环完成的一项最常见、最重要的任务:逐字符地读取来自文件或键盘的文本。
1)使用原始的cin进行输入
如果程序要使用循环来读取来自键盘的文本输入,则必须有办法知道何时停止读取。一种方法是选择某个特殊字符——有时被称为哨兵字符( sentinel character),将其作为停止标记。
#include int main()
{
using namespace std;
char ch;
int count = 0;
cout c< "Enter characters; enter # to quit : \n";
cin >> ch;
while (ch !='#')
{
cout c< ch;
++count;
cin >> ch;
}
cout << endl cc count c< " characters read'in";
return 0 ;
}
运行情况:
Enter characters; enter # to quit :
see ken run#really fast
seekenrun
9 characters read
为什么程序在输出时省略了空格呢?原因在 cin。读取 char值时,与读取其他基本类型一样,cin将忽略空格和换行符。因此输入中的空格没有被回显,也没有被包括在计数内。
更为复杂的是,发送给 cin 的输入被缓冲。这意味着只有在用户按下回车键后,他输入的内容才会被发送给程序。这就是在运行该程序时,可以在#后面输入字符的原因。按下回车键后,整个字符序列将被发送给程序,但程序在遇到#字符后将结束对输入的处理。