资源描述
C++Primer中文版(第4版)学习笔记
调用 GNU 或微软编译器
调用 GNU 编译器的默认命令是 g++:
$ g++ prog1.cc -o prog1
微软编译器采用命令 cl 来调用:
C:\directory> cl -GX prog1.cpp
acm pc2编译器搭配
下面是pc2配置:(以vc为编译环境)
配置环境变量:jdk设置:path=C:\Program Files\Java\jdk1.6.0\bin;
vc编译器设置:
path=C:\Program Files\Microsoft Visual Studio\VC98\Bin;
lib=C:\Program Files\Microsoft Visual Studio\VC98\Lib;
include=C:\Program Files\Microsoft Visual Studio\VC98\include
环境变量配置完成之后,设置下pc2就ok了!pc2设置如下:
compile com line: cl.exe {:mainfile}
Executable Filename: {:basename}.exe
program execution command line:{:basename}.exe
做到这些配置,基本上编译就不成问题了!注意,期间可以需要到C:\Program Files\Microsoft Visual Studio\COMMON\Tools路径下拷贝mspdb60.dll到C:\Program Files\Microsoft Visual Studio\VC98\Bin;!这个自己调试就Ok了!
访问 main 函数的返回值的方式和系统有关。不论 UNIX 还是 Windows 系统,执行程序后,必须发出一个适当的 echo 命令。UNIX 系统中,通过键入如下命令获取状态:
$ echo $?
要在 Windows 系统下查看状态,键入
C:\directory> echo %ERRORLEVEL%
再谈编译
编译器的部分工作是寻找程序代码中的错误。编译器不能查出程序的意义是否正确, 但它可以查出程序形式上的错误。下面是编译器能查出的最普遍的一些错误。
1、语法错误。程序员犯了 C++ 语言中的语法错误。下面代码段说明常见的语法错误;每个注释描述下一行的错误。
// error: missing ')' in parameter list for main
int main ( {
// error: used colon, not a semicolon after endl
std::cout << "Read each file." << std::endl:
// error: missing quotes around string literal
std::cout << Update master. << std::endl;
// ok: no errors on this line
std::cout << "Write new master." <<std::endl;
// error: missing ';' on return statement
return 0
}
2、类型错误。C++ 中每个数据项都有其相关联的类型。例如,值 10 是一个整数。用双引号标注起来的单词“hello”是字符串字面值。类型错误的一个实例是传递了字符串字面值给应该得到整型参数的函数。
3、声明错误。C++ 程序中使用的每个名字必须在使用之前声明。没有声明名字通常会导致错误信息。最常见的两种声明错误,是从标准库中访问名字时忘记使用“std::”,以及由于疏忽而拼错标识符名:
#include <iostream>
int main()
{
int v1, v2;
std::cin >> v >> v2; // error: uses " v "not" v1"
// cout not defined, should be std::cout
cout << v1 + v2 << std::endl;
return 0;
}
错误信息包含行号和编译器对我们所犯错误的简要描述。按错误报告的顺序改正错误是个好习惯,通常一个错误可能会产生一连串的影响,并导致编译器报告比实际多得多的错误。最好在每次修改后或最多改正了一些显而易见的错误后,就重新编译代码。这个循环就是众所周知的编辑—编译—调试。
从键盘输入文件结束符
操作系统使用不同的值作为文件结束符。Windows 系统下我们通过键入 control—z——同时键入“ctrl”键和“z”键,来输入文件结束符。Unix 系统中,包括 Mac OS—X 机器,通常用 control—d。
标准库的头文件用尖括号 < > 括起来,非标准库的头文件用双引号 " " 括起来。
我们能将值 20 定义成下列三种形式中的任意一种:
20 // decimal
024 // octal
0x14 // hexadecimal
以 0(零)开头的字面值整数常量表示八进制,以 0x 或 0X 开头的表示十六进制。
定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。
类似地,可通过在数值后面加 U 或 u 定义 unsigned 类型。
使用科学计数法时,指数用 E 或者 e 表示。默认的浮点字面值常量为 double 类型。在数值的后面加上 F 或 f 表示单精度。同样加上 L 或者 l 表示扩展精度
非打印字符的转义序列
newline
换行符
\n
horizontal tab
水平制表符
\t
vertical tab
纵向制表符
\v
backspace
退格符
\b
carriage return
回车符
\r
formfeed
进纸符
\f
alert (bell)
报警(响铃)符
\a
backslash
反斜线
\\
question mark
疑问号
\?
single quote
单引号
\'
double quote
双引号
\"
我们可以将任何字符表示为以下形式的通用转义字符:
\ooo
这里 ooo 表示三个八进制数字,这三个数字表示字符的数字值。下面的例子是用 ASCII 码字符集表示字面值常量:
\7 (bell) \12 (newline) \40 (blank)
\0 (null) \062 ('2') \115 ('M')
字符’\0’通常表示“空字符(null character)”,我们将会看到它有着非常特殊的意义。
同样也可以用十六进制转义字符来定义字符:
\xddd
C++ 的格式非常自由。特别是有一些地方不能插入空格,其中之一是在单词中间。特别是不能在单词中间断开一行。但可以通过使用反斜线符号巧妙实现:
// ok: A \ before a newline ignores the line break
std::cou\
t << "Hi" << st\
d::endl;
等价于
std::cout << "Hi" << std::endl;
可以使用这个特性来编写长字符串字面值:
// multiline string literal
std::cout << "a multi-line \
string literal \
using a backslash"
<< std::endl;
return 0;
}
注意反斜线符号必须是该行的尾字符——不允许有注释或空格符。同样,后继行行首的任何空格和制表符都是字符串字面值的一部分。正因如此,长字符串字面值的后继行才不会有正常的缩进。
C++ 关键字
asm
do
if
return
try
auto
double
inline
short
typedef
bool
dynamic_cast
int
signed
typeid
break
else
long
sizeof
typename
case
enum
mutable
static
union
catch
explicit
namespace
static_cast
unsigned
char
export
new
struct
using
class
extern
operator
switch
virtual
const
false
private
template
void
const_cast
float
protected
this
volatile
continue
for
public
throw
wchar_t
default
friend
register
true
while
delete
goto
reinterpret_cast
C++ 操作符替代名
and
bitand
compl
not_eq
or_eq
xor_eq
and_eq
bitor
not
or
xor
C++ 支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中:
int ival(1024); // direct-initialization
int ival = 1024; // copy-initialization
初始化内置类型的对象只有一种方法:提供一个值,并且把这个值复制到新定义的对象中。对内置类型来说,复制初始化和直接初始化几乎没有差别。
对类类型的对象来说,有些初始化仅能用直接初始化完成。要想理解其中缘由,需要初步了解类是如何控制初始化的。
变量初始化规则
内置类型变量的初始化
使用未初始化的变量是常见的程序错误,通常也是难以发现的错误。虽然许多编译器都至少会提醒不要使用未初始化变量,但是编译器并未被要求去检测未初始化变量的使用。而且,没有一个编译器能检测出所有未初始化变量的使用。
有时我们很幸运,使用未初始化的变量导致程序在运行时突然崩溃。一旦跟踪到程序崩溃的位置,就可以轻易地发现没有正确地初始化变量。
但有时,程序运行完毕却产生错误的结果。更糟糕的是,程序运行在一部机器上时能产生正确的结果,但在另外一部机器上却不能得到正确的结果。添加代码到程序的一些不相关的位置,会导致我们认为是正确的程序产生错误的结果。
建议每个内置类型的对象都要初始化。虽然这样做并不总是必需的,但是会更加容易和安全,除非你确定忽略初始化式不会带来风险。
类类型变量的初始化
每个类都定义了该类型的对象可以怎样初始化。类通过定义一个或多个构造函数来控制类对象的初始化。
如果定义某个类的变量时没有提供初始化式,这个类也可以定义初始化时的操作。它是通过定义一个特殊的构造函数即默认构造函数来实现的。这个构造函数之所以被称作默认构造函数,是因为它是“默认”运行的。如果没有提供初始化式,那么就会使用默认构造函数。不管变量在哪里定义,默认构造函数都会被使用。
变量的声明和定义
变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。
声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。可以通过使用extern关键字声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern:
extern int i; // declares but does not define i
int i; // declares and defines i
extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:
extern double pi = 3.1416; // definition
虽然使用了 extern ,但是这条语句还是定义了 pi,分配并初始化了存储空间。只有当 extern 声明位于函数外部时,才可以含有初始化式。
因为已初始化的 extern 声明被当作是定义,所以该变量任何随后的定义都是错误的:
extern double pi = 3.1416; // definition
double pi; // error: redefinition of pi
同样,随后的含有初始化式的 extern 声明也是错误的:
extern double pi = 3.1416; // definition
extern double pi; // ok: declaration not definition
extern double pi = 3.1416; // error: redefinition of pi
声明和定义之间的区别可能看起来微不足道,但事实上却是举足轻重的。
在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。
任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
下列程序段将会输出什么?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << i << " " << sum << std::endl;
【解答】
输出为:
100 45
for 语句中定义的变量i,其作用域仅限于for 语句内部。输出的i 值是for 语
句之前所定义的变量i 的值。
const
因为常量在定义后就不能被修改,所以定义时必须初始化:
const std::string hi = "hello!"; // ok: initialized
const int i, j = 0; // error: i is uninitialized const
引用
引用必须用与该引用同类型的对象初始化:
int ival = 1024;
int &refVal = ival; // ok: refVal refers to ival
int &refVal2; // error: a reference must be initialized
int &refVal3 = 10; // error: initializer must be an object
因为引用只是它绑定的对象的另一名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上:
const 引用
const 引用是指向 const 对象的引用:
const int ival = 1024;
const int &refVal = ival; // ok: both reference and object are const
int &ref2 = ival; // error: non const reference to a const object
可以读取但不能修改 refVal ,因此,任何对 refVal 的赋值都是不合法的。这个限制有其意义:不能直接对 ival 赋值,因此不能通过使用 refVal 来修改 ival。
类定义
使用 struct 关键字
C++ 支持另一个关键字 struct,它也可以定义类类型。struct 关键字是从 C 语言中继承过来的。
用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。
class Sales_item {
public:
// operations on Sales_item objects will go here
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
struct Sales_item {
// no need for public label, members are public by default
// operations on Sales_item objects
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
编译和链接多个源文件
我们可以按以下方式编译这两个文件:
$ CC -c main.cc Sales_item.cc # by default generates a.exe
# some compilers generate a.out
# puts the executable in main.exe
$ CC -c main.cc Sales_item.cc -o main
其中 $ 是我们的系统提示符,# 开始命令行注释。现在我们可以运行可执行文件,它将运行我们的 main 程序。
如果我们只是修改了一个 .cc 源文件,较有效的方法是只重新编译修改过的文件。大多数编译器都提供了分别编译每一个文件的方法。通常这个过程产生 .o 文件,.o 扩展名暗示该文件含有目标代码。
编译器允许我们把目标文件链接在一起以形成可执行文件。我们所使用的系统可以通过命令名 CC 调用编译。因此可以按以下方式编译程序:
$ CC -c main.cc # generates main.o
$ CC -c Sales_item.cc # generates Sales_item.o
$ CC main.o Sales_item.o # by default generates a.exe;
# some compilers generate a.out
# puts the executable in main.exe
$ CC main.o Sales_item.o -o main
头文件用于声明而不是用于定义
因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。
对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。
一些 const 对象定义在头文件中
当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些 const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的 const 变量。可在头文件中定义。
如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加 extern 声明,以使其能被多个文件共享。
下列声明和定义哪些应该放在头文件中?哪些应该放在源文件中?请解释原
因。
(a) int var ;
(b) const double pi = 3.1416;
(c) extern int total = 255 ;
(d) const double sq2 = squt (2.0) ;
【解答】
(a)、(c)、(d)应放在源文件中,因为(a)和(c)是变量定义,定义通常应放在源
文件中。(d)中的const 变量sq2 不是用常量表达式初始化的,所以也应该放在
源文件中。
(b)中的const 变量pi 是用常量表达式初始化的,应该放在头文件中。
不使用命名空间
#include <iostream>
int main()
{
int sum = 0, val = 1;
// keep executing the while until val is greater than 10
while (val <= 10) {
sum += val; // assigns sum + val to sum
++val; // add 1 to val
}
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
命名空间的 using 声明
using 声明可以在不需要加前缀 namespace_name:: 的情况下访问命名空间中的名字。using 声明的形式如下:
using namespace::name;
一旦使用了 using 声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间。
#include <string>
#include <iostream>
// using declarations states our intent to use these names from the namespace std
using std::cin;
using std::string;
int main()
{
string s; // ok: string is now a synonym for std::string
cin >> s; // ok: cin is now a synonym for std::cin
cout << s; // error: no using declaration; we must use full name
std::cout << s; // ok: explicitly use cout from namepsace std
}
using 指示
using namespace std;
标准库 string 类型
几种初始化 string 对象的方式
string s1;
默认构造函数 s1 为空串
string s2(s1);
将 s2 初始化为 s1 的一个副本
string s3("value");
将 s3 初始化为一个字符串字面值副本
string s4(n, 'c');
将 s4 初始化为字符 'c' 的 n 个副本
标准库 string 类型和字符串字面值
因为历史原因以及为了与 C 语言兼容,字符串字面值与标准库 string 类型不是同一种类型。这一点很容易引起混乱,编程时一定要注意区分字符串字面值和 string 数据类型的使用,这很重要。
// Note: #include and using declarations must be added to compile this code
int main()
{
string s; // empty string
cin >> s; // read whitespace-separated string into s
cout << s << endl; // write s to the output
return 0;
}
以上程序首先定义命名为 s 的 string 第二行代码:
cin >> s; // read whitespace-separated string into s
从标准输入读取 string 并将读入的串存储在 s 中。string 类型的输入操作符:
读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
读取字符直至再次遇到空白字符,读取终止。
读入未知数目的 string 对象
和内置类型的输入操作一样,string 的输入操作符也会返回所读的数据流。因此,可以把输入操作作为判断条件,这与我们在 1.4.4 节读取整型数据的程序做法是一样的。下面的程序将从标准输入读取一组 string 对象,然后在标准输出上逐行输出:
int main()
{
string word;
// read until end-of-file, writing each word to a new line
while (cin >> word)
cout << word << endl;
return 0;
}
使用 getline 读取整行文本
getline。这个函数接受两个参数:一个输入流对象和一个 string 对象。getline 函数从输入流的下一行读取,并保存读取的内容到不包括换行符。和输入操作符不一样的是,getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符,getline 也将停止读入并返回。如果第一个字符就是换行符,则 string 参数将被置为空 string。
getline 函数将 istream 参数作为返回值,和输入操作符一样也把它用作判断条件。例如,重写前面那段程序,把每行输出一个单词改为每次输出一行文本:
int main()
{
string line;
// read line at time until end-of-file
while (getline(cin, line))
cout << line << endl;
return 0;
}
由于 getline 函数返回时丢弃换行符,换行符将不会存储在 string 对象中。
string 对象的操作
.empty()
如果 s 为空串,则返回 true,否则返回 false。
s.size()
返回 s 中字符的个数
s[n]
返回 s 中位置为 n 的字符,位置从 0 开始计数
s1 + s2
把 s1 和s2 连接成一个新字符串,返回新生成的字符串
s1 = s2
把 s1 内容替换为 s2 的副本
v1 == v2
比较 v1 与 v2的内容,相等则返回 true,否则返回 false
!=, <, <=, >, and >=
保持这些操作符惯有的含义
string::size_type 类型
从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或如 2.2 节“建议”中所述的无符号整数。但事实上,size 操作返回的是 string::size_type 类型的值。我们需要对这种类型做一些解释。
任何存储 string 的 size 操作结果的变量必须为 string::size_type 类型。特别重要的是,不要把 size 的返回值赋给一个 int 变量。
和字符串字面值的连接
当进行 string 对象和字符串字面值混合连接操作时,+ 操作符的左右操作数必须至少有一个是 string 类型的:
string s1 = "hello"; // no punctuation
string s2 = "world";
string s3 = s1 + ", "; // ok: adding a string and a literal
string s4 = "hello" + ", "; // error: no string operand
string s5 = s1 + ", " + "world"; // ok: each + has string operand
string s6 = "hello" + ", " + s2; // error: can't add string literals
可用下标操作符分别取出 string 对象的每个字符,分行输出:
string str("some string");
for (string::size_type ix = 0; ix != str.size(); ++ix)
cout << str[ix] << endl;
cctype Functions
isalnum(c)
如果 c 是字母或数字,则为 True。
isalpha(c)
如果 c 是字母,则为 true。
iscntrl(c)
如果 c 是控制字符,则为 true
isdigit(c)
如果 c 是数字,则为 true。
isgraph(c)
如果 c 不是空格,但可打印,则为 true。
islower(c)
如果 c 是小写字母,则为 true。
isprint(c)
如果 c 是可打印的字符,则为 true。
ispunct(c)
如果 c 是标点符号,则 true。
isspace(c)
如果 c 是空白字符,则为 true。
isupper(c)
如果 c 是大写字母,则 true。
isxdigit(c)
如果是 c 十六进制数,则为 true。
tolower(c)
如果 c 大写字母,返回其小写字母形式,否则直接返回 c。
toupper(c)
如果 c 是小写字母,则返回其大写字母形式,否则直接返回 c。
建议:采用 C 标准库头文件的 C++ 版本
C++ 标准库除了定义了一些选定于 C++ 的设施外,还包括 C 标准库。C++ 中的头文件 cctype 其实就是利用了 C 标准库函数,这些库函数就定义在 C 标准库的 ctype.h 头文件中。
C 标准库头文件命名形式为 name 而 C++ 版本则命名为 cname ,少了后缀,.h 而在头文件名前加了 c 表示这个头文件源自 C 标准库。因此,cctype 与 ctype.h 文件的内容是一样的,只是采用了更适合 C++程序的形式。特别地,cname 头文件中定义的名字都定义在命名空间 std 内,而 .h 版本中的名字却不是这样。
通常,C++ 程序中应采用 cname 这种头文件的版本,而不采用 name.h 版本,这样,标准库中的名字在命名空间 std 中保持一致。使用 .h 版本会给程序员带来负担,因为他们必须记得哪些标准库名字是从 C 继承来的,而哪些是 C++ 所特有的。
标准库 vector 类型
vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和 string 对象一样,标准库将负责管理与存储元素相关的内存。我们把 vector 称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种类型的。
vector 是一个类模板(class template)。使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存 string 对象的 vector,或保存 int 值的 vector,又或是保存自定义的类类型对象(如 Sales_items 对象)的 vector。
声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以 vector 为例,必须说明 vector 保存何种对象的类型,通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:
vector<int> ivec; // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector<int> 和 vector<string> 都是数据类型。
vector 对象的定义和初始化
vector<T> v1;
vector 保存类型为 T 对象。默认构造函数 v1 为空。
vector<T> v2(v1);
v2 是 v1 的一个副本
展开阅读全文