Overview of C++
Fisrt Program
using namespace std;
/*
powersOfTwo
*/
/*
Function Prototypes
*/
int raiseToPower(int n, int k);
/*
Main program
*/
int main() {
int limit;
cout << "This program lists powers of two." << endl;
cout << "Enter exponent limit: ";
cin >> limit;
for (int i = 0; i <= limit; i++) {
cout << "2 to the " << i << "="
<< raiseToPower(2, i) << endl;
}
return 0;
}
/*
Function: raiseToPower
Usage: int p = raiseToPower(n, k);
-------------------------------------
returns the integer n raised to the kth power
*/
int raiseToPower(int n, int k) {
int result = 1;
for (int i = 0; i < k; i++) {
result *= n;
}
return result;
}
标识符cout
表示console output stream,称为控制台输出流。
标识符cin
表示console input stream,称为控制台输入流,由于cin >> limit
中limit
被声明为整形变量,操作符>>
会自动地将用户输入的字符类型的值转化为相应的整数值。
enum
和 switch
using namespace std;
int main() {
enum Graph {
DG = 1,
UDG,
DN,
UDN
};
/*
Switch 语句的使用
*/
int option;
cout << "Enter a number: ";
cin >> option;
switch (option)
{
case DG: cout << "Directed Graph" << endl;
break;
case UDG: cout << "Undirected Graph" << endl;
break;
case DN: cout << "Directed Network" << endl;
break;
case UDN: cout << "Undirected Network" << endl;
break;
default: cout << "Invalid Number" << endl;
break;
}
}
注意怎么使用枚举,以及怎么使用switch
和break
。
Functions and Libraries
函数原型(Prototype)
当C++编译器在程序中遇到函数调用时,它需要一些函数的相关信息来确保生成正确的程序代码。在大多数情况下,编译器不需要了解函数体所包含的所有语句,它所需要了解的仅是函数所需要的形参以及函数的返回值类型。这些信息通常由函数原型提供,他由函数的第一行后跟一个分号构成。
如前面例子中raiseToPower
int raiseToPower(int n, int k); // Prototype Function
重载(Overloading)
C++中,函数名相同但函数的参数列表不同是合法的,当编译器遇到调用函数的函数名指代不唯一的情况时,编译器会检查调用函数时所传实参并选择最合适的函数版本。几个使用相同名字的不同版本函数称为函数名的重载。函数的形参模型仅考虑形参数量及其类型,而不考虑其形参名称,函数的形参模型被称为函数签名。
int abs(int x) {
return x < 0 ? -x : x;
}
double abs(double x) {
return x < 0 ? -x : x;
}
默认形参数
C++可以指定函数中的某些形参具有默认值。形参变量依旧出现在函数的第一行,但函数声明中已赋值的形参可以在函数调用时不给其实值,这种具有默认值的形参称为默认形参数(default parameter)。
值得注意的是,默认性参数的函数在C++中存在过度使用的问题,通常我们更倾向于使用C++中的函数重载来完成与默认形参数相同的功能。
/* Use default parameter Function */
void setInitialLocation(double x = 0, double y = 0);
/* Use Function Overloading */
void setInitialLocation(double x, double y);
void setInitialLocation() {
setInitialLocation(0, 0);
}
引用调用(Call by Reference)
引用参数是在参数类型与参数名中间加一个&
。
/*
Call by Reference
*/
void quadratic(int &x) {
x *= x;
}
int main() {
cout << "Enter a value for x:";
int x;
cin >> x;
cout << "Initial value is: " << x << endl;
quadratic(x);
cout << "Quadratic value is: " << x << endl;
}
/*
compile results:
Enter a value for x:78
Initial value is: 78
Quadratic value is: 6084
*/
在C++中,最常使用引用调用的情况是函数需要返回多于一个值的情况。
接口(Interface)与实现(Implementation)
当你定义一个C++库时,你需要提供库的两个部分,首先你必须要定义接口。他可以让库用户在不了解库实现细节的情况下使用库中的函数。第二,你需要定义库的具体实现,它说明库的底层实现细节。一个典型的接口可以提供多种的定义,包括函数定义、类型定义和常量定义。每一个定义都成为一个接口条目(interface entry)。
在C++中,接口和实现通常写在两个不同的文件中,定义接口的文件后缀名是.h
,也称为头文件,定义实现的文件也取同样的名字,但其后缀为.cpp
。根据这些原则,error
库将在error.h
中定义,并在error.cpp
中实现。
定义error
库
接口
/*
File: error_simple.h
---------------------
This file defines a simple function for reporting errors.
*/
#pragma once
#ifndef _error_simple_h
#define _error_simple_h
/*
Fcuntion: error
Usage: error(msg);
------------------
Writes the string msg to the cerr stream and then exits the program
with a standard status code indicating failure. The usual pattern for
using error is to enclose the call to error inside an if statement that
checks for a pariticular condition, which might look something like that.
if (divisir == 0) error("Division by zero");
*/
void error(std::string msg); // 说明string类型来源于命名空间std,接口一般来说是代码行using namespace std
// 之前读取的,因此如果不使用std::修饰符来明确表示,我们将不能使用命名空间中的标识符
#endif // !_error_simple_h
实现
/*
File: error_simple.cpp
----------------------
This file implements the error_simple.h interface
*/
#include "stdafx.h"
#include <cstdlib>
#include <string>
#include "error_simple.h"
using namespace std;
/*
Implementation notes: error
---------------------------
This function writes out the error message to the cerr stream and
the exits the program. The EXIT_FAILURE constant is defined in
<cstdlib> to represent a standard failure code.
*/
void error(string msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
}
导出数据类型
/*
File: direction.h
-----------------
This interface exports an enumerated type called Direction whose
elements are the four compass points: NORTH, EAST, SOUTH, and WEST.
*/
#pragma once
#ifndef _direction_h
#define _direction_h
#include <string>
/*
Type: Direction
---------------
This enumerated type is used to represent the four compass direction.
*/
enum Direction { NORTH, EAST, SOUTH, WEST };
/*
Function: letfFrom
Usage: Direction newdir = leftFrom(dir);
-------------------
Returns the direction that is to the left of the argument.
For example, leftFrom(NORTH) returns WEST.
*/
Direction leftFrom(Direction dir);
/*
Function: rightFrom
Usage: Direction newdir = rightFrom(dir);
-------------------
Returns the direction that is to the right of the argument.
For example, leftFrom(NORTH) returns EAST.
*/
Direction rightFrom(Direction dir);
/*
Function: directionToString
Usage: string str = direction as a string
-----------------------------------------
returns the name of the direction as a string.
*/
std::string directionToString(Direction dir);
#endif // !_direction_h
/*
File: direction.cpp
-------------------
This file implements the direction.h interface
*/
#include <string>
#include "direction.h"
using namespace std;
/*
Implementation notes: leftFrom, rightForm
------------------------------------------
These functions use the remainder operator to cycle through the
internal values of the enumeration type. Note that the leftFrom
function cannot subtract 1 from the direction because the result
might then be negative; adding 3 achieves the same effect but
ensures that the values remain positive.
*/
Direction leftForm(Direction dir) {
return Direction((dir + 3) % 4);
}
Direction rightForm(Direction dir) {
return Direction((dir + 1) % 4);
}
/*
Implementation Notes: directionToString
----------------------------------------
Most C++ compilers require the default clause to make sure that this
function always returns a string, even if the direction is not one
if the legal values.
*/
string directionToString(Direction dir) {
switch (dir)
{
case NORTH: return "NORTH";
case SOUTH: return "SOUTH";
case EAST: return "EAST";
case WEST: return "WEST";
default:
return "???";
}
}
随机数库的设计
标准库的伪随机数
/*
File: RandTest.cpp
-------------------
This program tests the random number generator in C++ and produces
the value used in the examples in the text.
*/
#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;
const int N_TRIALS = 10;
int main() {
cout << "On this computer, RAND_MAX is " << RAND_MAX << endl;
cout << "The first " << N_TRIALS << " calls to rand:" << endl;
for (int i = 0; i < N_TRIALS; i++) {
cout << setw(10) << rand() << endl;
}
return 0;
}
编译运行如下:
On this computer, RAND_MAX is 32767
The first 10 calls to rand:
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
cout << setw(10) << rand() << endl;
应该是设置流的长度为10;
Strings
Using strings as abstract values
在C++中设计者提供了string
类作为基本的数据类型。
定义一个字符串常量如下:
const string ALPHABET = "abcdefghijklmnopqrstuvwxyz";
下面是hello world的一个测试版本
int main() {
/*
测试cin和cout的作用
*/
string name;
cout << "Enter your name: ";
cin >> name;
cout << "Your name is: " << name << endl;
return 0;
}
而运行的时候,我们发现:
Enter your name: Jakie Peng
Your name is: Jakie
程序在输出的时候忽略了我的姓氏,而只输出了我的名字,因此,我们需要知道,这里的话cin
在截取字符串的时候在遇到whitespace(空白符)的时候会停止,如果想要获取一整行,则我们需要使用getline()
函数。
修改的程序版本如下:
int main() {
string name;
cout << "Enter your name: ";
getline(cin, name);
cout << "Your name is: " << name << endl;
return 0;
/*
output:
Enter your name: Jakie Peng
Your name is: Jakie Peng
*/
}
String operations
应该要记住的是在C++中string
是一个类,所以我们使用有关字符串的方法实际上是调用实例的方法,具体操作如下所示:obj.method(*para)
。
void main() {
string name = "Jakie Peng";
string lover = "Uozoyo";
string lastName = "Peng";
// 获取字符串的长度
cout << "字符:" << name << "长度为:" << name.length() << endl;
// opreator reload 操作符重载
// 字符串拼接
cout << "字符串拼接结果为:" << name + lover << endl;
// 从一个字符串中读取选中字符,你获取的是该字符的引用,因此可以对其进行修改。
cout << "从 name 中选出第2个字符,其值为" << name.at(1) << endl;
// 提取字符串的子串
cout << "name字符第2个字符后面的内容为" << name.substr(2) << endl;
}
cctype
library
由于字符串是由字符组成的,处理字符串中的单个字符而不是整个字符的操作就显得非常重要。<cctype>
库提供了处理字符串中字符的各种函数。
Streams
C++中的一种重要的数据结构,并用这种数据结构去管理来自或流向某个数据源的信息流。
Formatted output
C++中,产生格式化输出的最简单的方式是<<
操作符,整个操作符被叫做插入操作符(Insertion Opeartor),因为它有将数据插入到一个流的作用。而我们常使用的endl
在C++中被称作流操纵符(Manipulator),流操作符仅是一种用于控制格式化输出的一种特定类型值的有趣名称。
1. endl
将行结束序列插入到输出流,并确保输出的字符能被写到目的地流中。
2. setw(n)
将下一个输出字段的宽度设置为n
个字符,如果输出值的宽域小于n
,则使用空白代替。这种性质是短暂的。
函数在<iomanip>
库中定义。
int main() {
int iteration_num = (int)rand() % 11; // 迭代次数
for (int i = 0; i < iteration_num; ++i) {
// 测试格式化输出的一些东西
cout << setw(4) << i << endl;
}
cout << 1111 <<< endl;
return 0;
}
输出结果如下:
0
1
2
3
4
5
6
7
1111
请按任意键继续. . .
3. setprecision(digits)
将输出流的精度设置为digits
。这个设置是永久性的。
如果没有设置scientific
和fixed
则digits
表示有效数字个数,如果设置了,digits
则表示小数点个数。
-
未设置
scientific
和fixed
code:
int main() { int iteration_num = (int)rand() % 11; // 迭代次数 for (int i = 0; i < iteration_num; ++i) { // 测试格式化输出的一些东西 cout << setprecision(3) << rand()*0.0004 << endl; } return 0;
输出为:
7.39 2.53 10.6 7.67 6.29 4.59 11.7 10.8 请按任意键继续. . .
注意观察,其有效数字都为3位。
-
设置了
scientific
和fixed
code:
int main() { int iteration_num = (int)rand() % 11; // 迭代次数 for (int i = 0; i < iteration_num; ++i) { // 测试格式化输出的一些东西 cout << setprecision(3) << rand()*0.0004 <<fixed<< endl; } return 0; }
7.39 2.534 10.600 7.668 6.290 4.591 11.743 10.785 请按任意键继续. . .
注意观察,其小数点为3位。
4. setfill(ch)
为流设置填充字符ch
。这个设置是永久性的。
int main() {
int iteration_num = (int)rand() % 11; // 迭代次数
for (int i = 0; i < iteration_num; ++i) {
// 测试格式化输出的一些东西
cout << setfill('~') << setw(4) << i << endl;
}
return 0;
}
~~~0
~~~1
~~~2
~~~3
~~~4
~~~5
~~~6
~~~7
请按任意键继续. . .
上面可以看出字符~
代替了原本填充的。
5. left
输出字段左对齐,这意味着任何填充字符都在输出值之后插入。这个设置是永久性的。
int main() {
int iteration_num = (int)rand() % 11; // 迭代次数
for (int i = 0; i < iteration_num; ++i) {
// 测试格式化输出的一些东西
cout << setfill('~') << setw(4) << left << i << endl;
}
return 0;
}
0~~~
1~~~
2~~~
3~~~
4~~~
5~~~
6~~~
7~~~
由上可知,空白字符在i
之后插入。
6. right
输出字段右对齐,这意味着任何填充字符都在输出值之前插入。这个设置是永久性的。
int main() {
int iteration_num = (int)rand() % 11; // 迭代次数
for (int i = 0; i < iteration_num; ++i) {
// 测试格式化输出的一些东西
cout << setfill('~') << setw(4) << right << i << endl;
}
return 0;
}
~~~0
~~~1
~~~2
~~~3
~~~4
~~~5
~~~6
~~~7
请按任意键继续. . .
注意与left
做出区分。
7. fixed
输出的数字应该完整的呈现,而不使用科学计数法。这个设置是永久性的。
8. fixed
使用科学计数法呈现数字。这个设置是永久性的。
int main() {
int iteration_num = (int)rand() % 11; // 迭代次数
for (int i = 0; i < iteration_num; ++i) {
// 测试格式化输出的一些东西
cout << scientific << setprecision(2) << rand()*0.0001 << endl;
}
return 0;
}
1.85e+00
6.33e-01
2.65e+00
1.92e+00
1.57e+00
1.15e+00
2.94e+00
2.70e+00
9. showpront
和noshowpoint
是否强制显示小数点。这个设置是永久性的。
10. showpos
和noshowpos
是否显示正数前面的正号。这个设置是永久性的。
Formatted input
C++格式化输入已经嵌入了操作符>>
,这个操作符称之为提取操作符(Extraction Operator)。
下面介绍几个输入流操作符:
1. skipws
和noskipws
这两个流操作符控制提取操作符>>
在读取一个值之前是否忽略空白字符。这个设置是永久性的。
int main() {
char ch;
cout << "Enter a single character: ";
cin >> skipws >> ch;
cout << "The character you entered is: " << ch << endl;
return 0;
}
Enter a single character: 5
The character you entered is: 5
我们可以很明显的看出,我们输入5
之前输入了很多空格,然而最后结果返回的还是5
而不是一个空格,就证明skipws
已经忽略了这些空格。
2. ws
从输入流中读取字符,直到它不属于空白字符。
int main() {
char ch;
cout << "Enter a single character: ";
cin >> noskipws >> ws >> ch;
cout << "The character you entered is: " << ch << endl;
return 0;
}
我们可以看到,这段代码的运行结果和上一个使用skipws
表现出来的结果如出一辙。
Enter a single character: 5
The character you entered is: 5
Data files
使用文件流
在C++中,读或写一个文件要求遵循以下步骤:
- 声明一个指向某个文件的流变量。
- 打开文件。
- 传输数据
- 关闭文件
下面代码时读取一个在当前文件夹中的test.txt
文件的命令。
int main() {
/*
文件流操作 fstream
*/
ifstream infile;
infile.open("./test.txt");
char ch;
while (infile.get(ch))
{
cout << ch;
}
cout << endl;
infile.close();
return 0;
}
这段代码输出如下:
This is a TEST FILE written by Jakie Peng at 2018-8-5 21:03:46
此外,这里需要注意一点,如果我们使用ifstream.open(var)
的时候,这个var
是我们之前定义的一个字符串的话,则要使用var.c_str()
。
string var = "./test.txt";
ifstream infile;
infile.open(var.c_str());
/*
为了保证与C语言的兼容,C++文件操作在这里传递的是一个C语言类型的字符串,所以如果是C++类型的
字符串变量需要使用c_str()的方法进行转换。
*/
下面是个更为详细的例子:
/*
File: ShowFileContents.cpp
--------------------------
This program displays the contents of a file chosen by the user.
*/
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
/* Function Prototypes */
string promptUserForFile(ifstream &infile, string prompt = "");
/* Main program */
int main() {
ifstream infile;
promptUserForFile(infile, "Input file: ");
char ch;
while (infile.get(ch)) {
cout.put(ch);
}
infile.close();
return 0;
}
/*
Function: promptUserForFile
*/
string promptUserForFile(ifstream & infile, string prompt)
{
while (true)
{
cout << prompt;
string filename;
getline(cin, filename);
infile.open(filename.c_str());
if (!infile.fail()) return filename;
infile.clear(); // 确保在再次调用的时候,流的故障位被清除。
cout << "Unable to open that file. Try again." << endl;
if (prompt == "") prompt = "Input file: ";
}
}
字符串流
鉴于文件和字符串都是字符序列,很自然的程序语言提供了一个sstream
库,其提供了几种类允许你将一个流和一个字符串值关联起来。
/* Function Prototypes */
int stringToInterger(string str);
int main() {
string num = "x4";
cout << stringToInterger(num);
}
/*
Function: stringToInterger
*/
int stringToInterger(string str)
{
// 实现声明一个istringstream对象
istringstream stream(str); // 使用str初始化stream对象
int value;
stream >> value >> ws;
if (!stream.eof() || stream.fail()) {
cerr << "stringToInteger: Illegal integer format";
}
return value;
}
Class hierarchies
C++已经算用继承比较少的面向对象的一门语言。
Collections
我们这里会介绍5个类——Vector
、Stack
、Queue
、Map
和Set
。这些类被称为集合类(Collections classes)。
1. The Vector class
其提供了一种类似于在早期编程中曾经遇到过的具有数组功能的机制。而C++也支持数组,然而C++中的数组有若干缺点:
- 数组被分配具有固定大小的内存,以至于程序员不能在以后对其大小进行改变。
- 即使数组有初始大小,C++也不允许程序员获取这个大小(这里表述是否有问题
sizeof
函数应该是可以获取分配内存大小的),所以程序员需要手动分配一个位置记录元素个数。 - 传统的数组不支持插入和删除数组元素的操作。
- C++对数组越界不做检查。C++只会查看内存地址。
指定vector
的基类型
在C++中,集合类通过在类的名字后添加一对包含类型名称的尖括号来指定其包含对象的类型。例如:vector<int>
就表示其元素类型为整形。我们一般将尖括号里面的类型称为基类型(base type),而包含基类型的类常被称为参数化的类(parameterized class),而在C++中常被称为模板(template)。
声明vector
变量
vector<int> vec;
vector
的操作(C99和C11差异较大)
差异的一个关键原因在于C11使用了迭代器(iterators)。
-
添加元素
push_back()
-
插入元素
insert()
-
删除元素
erase()
查询和修改元素
选择元素
/* Function Prototypes */
void printVector(vector<int> &vec, string prompt="");
int main() {
/* declare an empty vector */
vector<int> vec;
printVector(vec, "Empty Vector: ");
vec.push_back(rand());
printVector(vec, "Add an Element to The End: ");
vec.push_back(rand());
vec.push_back(rand());
printVector(vec, "Three Elements in The Vector: ");
// 设置迭代器的值
vector<int>::iterator v = vec.begin();
vec.insert(v, 0);
printVector(vec, "Add 0 on The Fisrt Pos: ");
// delete
vec.erase(vec.begin());
printVector(vec, "Delete 0 Pos: ");
// modify the all elements
for (int i = 0; i < vec.size(); ++i) {
vec[i] = vec[i] % 10;
}
printVector(vec, "Mod 10: ");
// 预定义vector的大小
vector<int> vec2(10);
printVector(vec2, "Pre-defined vector with 10 elements: ");
return 0;
}
/*
Function: printVector
------------------------
Quick print the vector to the screen.
*/
void printVector(vector<int>& vec, string prompt)
{
if (prompt == "") prompt = "Vector info: ";
cout << prompt << "[";
for (int i = 0; i < vec.size(); i++) {
if (i > 0)cout << ",";
cout << vec[i];
}
cout << "]" << endl;
}
输出结果如下:
Empty Vector: []
Add an Element to The End: [41]
Three Elements in The Vector: [41,18467,6334]
Add 0 on The Fisrt Pos: [0,41,18467,6334]
Delete 0 Pos: [41,18467,6334]
Mod 10: [1,7,4]
Pre-defined vector with 10 elements: [0,0,0,0,0,0,0,0,0,0]
请按任意键继续. . .