前言

这一章是集合了第二节课的内容(春节前)和这个星期的内容,主要讲的是C与C++ 的一些区别,C++ 的一些特征,处理数据的情况和各种复合类型。因为太久没写了,所以这篇文章估计会很长~😉

C++是一种面向对象的编程语言,面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,也即使得描述问题的问题空间与问题的解决方案空间在结构上尽可能一致,把客观世界中的实体抽象为问题域中的对象。

所以为什么学习面向对象的程序设计没有对象可以面对~

那么直接进入正题。


Setting Out to C++

预处理器与名称空间

预处理器

C++和C一样,也使用一个预处理器,该程序在进行主编译之前对源文件进行处理。C语言的传统是,头文件使用扩展名.h, 将其作为一种通过名称标识文件类型的简单方式。

C++对此进行了改动,C++头文件则没有扩展名。C++对一些原本的C的头文件进行了修改增加,即将原本的头文件xxx.h改为了cxxx来适应C++的形式。例如C中math.h到了C++中就是cmath。

一般常见的C++头文件还有:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<string>
#include<vector>
#include<queue>

等等,这里就不一一列举。

名称空间

如果使用iostream, 则应使用下面的特征空间编译指令来使iostream中的定义对程序部分(全局)可用:

using namespace std;

名称空间支持是一项C++特性, 旨在让您编写大型程序以及将多个厂商现有的代码组合起来的程序时更容易分辨程序内容。按照这种方式,类、函数和变量便是C++编译器的标准组件,它们现在都被放在名称空间std中。

还有一种写法,可以省略using指令。例如std::cout<<std::endl;但是这种写法麻烦在每一句语句之前都要加入名称空间。

所以我们一般在头文件的下方或者在main函数刚开始就添加using namespace std;以供之后均使用这个名称空间。

输入输出

cout

相对于C语言的scanf和printf的标准流输出,C++中引入了一种新的输入输出方式,即cin和cout。

例如我们要输出一句话,我们可以这样使用cout语句:

cout << "Come up and C++ me some time.";  // message

双引号括起的部分是要打印的消息。在C++ 中, 用引号括起的一系列字符叫做字符串, 因为它是由若干字符组合而成的。<<符号表示该语旬将把这个字符串发送给cout: 该符号指出了信息流动的路径。

(PS:输出字符单引号双引号都可以,使用引号内的引号会被当做字符输出,例如:cout<<"Lily's book.";将会输出Lily's book.

为什么cout可以做到这些呢,是因为cout有一个接口,这个可能会在之后解释。

而这个插入运算符(<<) 看上去就像按位左移运算符(<<) ,这是一个运算符重载的例子,通过重载,同一个运算符将有不同的含义。编译器通过上下文来确定运算符的含义。

而输入里面还有一些特殊的符号:endl. cout << endl; // start a new line

endl是一个特殊的C++符号;表示一个重要的概念:重起一行。在输出流中插入endl 将导致屏幕光标移到下一行开头。诸如endl 等对于cout来说都是有特殊含义的特殊符号被称为控制符(manipulator)。和cout一样,endl 也是在头文件iostream 中定义的,且位于名称空间std中。

当然,C++还提供了另一种在输出中指示换行的旧式方法,即 C 语言的符号\ncout<<"Hello\n";

它们有什么区别呢?endl 确保程序继续运行前刷新输出(立刻换行),\n则不一定。

当然,cout还可以拼接在一起,例如:

cout << "Now you have " << carrots << " carrots." << endl;

这样能够将字符串输出和整数输出合并为一条语句。

说完了输出,我们接下来来看输入cin。

cin

我们输入一个值到变量里,例如carrots,我们可以用cin读入:

cin>>carrots;

表示信息从cin流入carrots里。cin 使用>>运算符从输入流中抽取字符。所以通常,需要在运算符右侧提供一个变量,以接收抽取的信息。

那我们可以类比一下,输出时,<<运算符将字符串插入到输出流中。即符号<<和>>被选择用来指示信息流的方向。与cout一样, cin 也是一个智能对象。它可以将通过键盘输入的—系列字符(即输入)转换为接收信息的变量能够接受的形式。

而cin有两种常用的成员函数

与字符串输入一样,有时候使用 cin>> 读取字符也不会按我们想要的结果行事。

例如,因为它会忽略掉所有前导白色空格,所以使用 cin>> 就不可能仅输入一个空格或回车符。除非用户输入了空格键、制表符之外的其他字符,否则程序将不可能通过 cin 语句继续执行。因此,要求用户“按回车键继续”的程序,不能使用 >> 运算符只读取按回车键的行为。

cin 对象有一个名为 get 的内置函数很有帮助。get 函数是内置在 cin 对象中的,所以可称之为 cin 的一个成员函数。如果我们需要用户单独输入一个空格之类的情况,就可以用cin.get();

我们使用get函数,需要这样写:

cin.get();

get 成员函数读取单个字符,包括任何白色空格字符。当然我们也可以把这个字符读到变量里,例如:

cin.get (ch);
ch = cin.get();

使用 C++ 字符数组与使用 string 对象还有另一种不同的方式,就是在处理它们时必须使用不同的函数集。例如,要读取一行输入,必须使用 cin.getline 而不是 getline 函数。这两个的名字看起来很像,但它们是两个不同的函数,不可互换。

与 getline 一样,cin.getline 允许读取包含空格的字符串。它将继续读取,直到它读取至最大指定的字符数,或直到按下了回车键。例如:

cin.getline(sentence, 20);

第一个参数是要存储字符串的数组的名称。第二个参数是数组的大小。读取的字符数将比该数字少一个,为 null 终止符留出空间。null 终止符将自动放在数组最后一个字符的后面。

当然我们也可以自定义终止字符,即第三个参数。例如我们以']'结束:

cin.getline(sentence, 20 , ']');

函数

这一部分很多都和C语言一样,就不再赘述。在这里我们来说一点不一样的。

C++库函数存储在库文件中。编译器编译程序时,它必须在库文件搜索您使用的函数。至于自动搜索哪些库文件,将因编译器而异。在<cmath>中,可能编译器不能自动搜索数学库,这时候需要我们在编译的时候添加-lm

例如:g++ sqrt.cpp -lm

其他的函数使用都和C语言差不多。


Dealing with Data

面向对象编程(OOP) 的本质是设计并扩展自己的数据类型。设计自己的数据类型就是让类型与数据匹配。如果正确做到了这一点,将会发现以后使用数据时会容易得多。然而, 在创建自己的类型之前, 必须了解并理解C++内置的类型,因为这些类型是创建自己类型的基本组件。

这一部分大多与C语言相同,例如命名规则,整形、浮点型、字符型等。我们就来介绍与C语言不同的部分。

bool类型

C++中引入了bool类型,bool变量只包含true(1)或false(0).另外任何数字值或指针值都可以被隐式转换。即非零非空为true,0和空为false。

例如:

bool a=-10;//true
bool b=0;//false

Compound Types

数组

C++的数组和C语言的数组一样,不过还增加了一些功能。例如在C++11中增加的:

double earnings[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // is ok, drop =
unsigned int counts[10] = {} ; // if {} is empty, all elements are 0
float balances[100] { }; // all elements set to 0

只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组。

和C一样,一个数组内的数据类型必须是相同的,所以下面的情况都是被允许的。

long plifs[ ] = { 25, 92, 3.0}; // not allowed
char slifs[4] {'h', 'i', 1122011, '\0'} ; // not allowed

String类

使用string

C++98 标准通过添加string 类扩展了C++库,string 类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。

我们通过下面这个例子来看string类和传统字符串的使用区别:

#include <iostream>
#include <string> // make string class available
int main()
{
    using namespace std;
    char charr1[20];            // create an empty array
    char charr2[20] = "jaguar"; // create an initialized array
    string str1;                // create an empty string object
    string str2 = "panther";    // create an initialized string

    cout << "Enter a kind of feline: "; cin >> charr1;
    cout << "Enter another kind of feline: "; 
    cin >> str1;                // use cin for input
    cout << "Here are some felines:\n";
    cout << charr1 << " " << charr2 << " "
         << str1 << " " << str2 // use cout for output
         << endl;
    cout << "The third letter in " << charr2 << " is "
         << charr2[2] << endl;
    cout << "The third letter in " << str2 << " is "
         << str2[2] << endl;    // use array notation
    return 0; 
}

这个程序让我们知道,和传统的字符数组一样,string类可以直接输入,而且同样的可以使用下标运算。

String类和字符数组之间的主要区别是,可以将string类声明为简单变量而不是数组类设计让程序能够自动处理string的大小。

这使得与使用数组相比, 使用string对象更方便、更安全。从理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string 类变点是一个表示字符串的实体

使用string 类时,某些操作比使用数组时更简单。

赋值、拼接和附加

我们知道不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象

例如:

string s1 = "penguin";
string s2,s3;
s2 = s1;

这样可以简便字符串操作,如果是传统的情况,只能使用一个中转数组或一位一位赋值。

string 类还简化了字符串合并操作。可以使用运算符+将两个string对象合并起来,还可以使用运算符+将字符串拼加到string 对象的末尾。

例如:(接上面)

s3 = s1 + s2;//s2拼在s1后面
s1 += s2;
s2 += " for a day";

在string类之前,我们只能使用:

char charr1[20]; 
char charr2[20] = "jaguar";
strcpy(charr1, charr2);     // copy charr2 to charr1
trcat(charr1, " juice");   // add juice to end of charr1

长度

我们之前计算字符串长度一般是用sizeof运算,在string类中我们可以以成员.size()的方式访问。

int len1 = str1.size();     // obtain length of str1
int len2 = strlen(charr1);  // obtain length of charr1

指针和自由存储空间

指针我们之前在指针专题的时候已经详细介绍了,这里就不再重复,我们直接进入存储空间。

在C 语言中,可以用库函数malloc()来分配内存; 在C++中仍然可以这样做,但C++还有更好的方法new 运算符。

在运行阶段为一个int 值分配未命名的内存, 并使用指针来访问这个值。程序员要告诉new, 需要为哪种数据类型分配内存: new 将找到一个长度正确的内存块,并返回该内存块的地址。

int * pt = new int;         // allocate space for an int

new int 告诉程序,需要适合存储int 的内存。new 运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址.接下来,将地址赋给pn, pn 是被声明为指向int 的指针。现在, pn 是地址,而*pn 是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:   

int higgens;
int *pt= &higgens;

new 从被称为堆(heap) 或自由存储区(free store)的内存区域分配内存,与常规变量声明分配的内存块不同。

当需要内存时,可以使用new来请求,使用完内存后要使用delete来释放内存。使用delete 时,后面要加上指向内存块的指针。

例如:

double * p1 = new double; // new
delete p1;// delete

我们知道这些基本的之后就可以使用new来创建动态数组了。

在C++中,创建动态数组很容易;只要将数组的元素类型和元素数目告诉new 即可。

必须在类型名后加上方括号,其中包含元素数目。例如,要创建一个包含10 个int 元素的数组,可以这样做;

int *p=new int [10];//get a block of 10 ints

new 运算符返回第一个元素的地址。在这个例了中,该地址被赋给指针p.

当程序使用完new 分配的内存块时,应使用delete 释放它们。

delete [] p; // free a dynamic array

方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。如果使用new 时,不带方括号,则使用delete 时,也不应带方括号。如果使用new 时带方括号,则使用delete时也应带方括号。

创建动态数组后,如何使用它呢?

如何访问其中的元索呢?第一个元素不成问题。由于p的名称指向数组的第1个元素,因此*p 是第1 个元素的值。访问其他的元素时只需要使用下表运算即可,例如访问第三个元素,可以写成*p[2]。


vector、array对象

vector

模板类vector 类似于string 类,也是一种动态数组。您可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。它是使用new 创建动态数组的替代品。

首先,要使用vector 对象,必须包含头文件vector。其次, vector 包含在名称空间std 中,因此您可使用using 编译指令、using 声明或std::vector。第三,模板使用不同的语法来指出它存储的数据类型。第四, vector 类使用不同的语法来指定元素数。

例如:

int n;
vector<int> vi(n);//创建了一个长度为n的动态数组

vector 类的功能比数组强大, 但付出的代价是效率稍低。如果需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。

array

与数组一样, array 对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便, 更安全。要创建array 对象,需要包含头文件array

const int n=10;
array<int, 5> ai;// create array object of 5 ints
array<int, n> bi;// create array object of n=10 ints

与创建vector 对象不同的是, n不能是变量。

首先无论是数组、 vector还是array对象,都可使用标准数组表示法来访问各个元索;其次,从地址可知array对象和数组存储在相同的内存区域 (即栈) 中,而vector对象存储在另一个区域(自由存储区或堆);第三, 可以将一个array对象赋给另一了array对象,而数组只能一个个赋值。

array有一个at()函数,作用和使用[]进行下标访问差不多。唯一的区别就是at()如果出现越界,则会报错。但是下标运算时不会。


后记

在这一章里,介绍了C++中预处理器、输入输出、处理数据、复合结构等于C语言的不同之处,算是正式的踏入学习C++的门了。

这里的一切都有始有终,却能容纳所有的不期而遇和久别重逢。
最后更新于 2024-01-14