第3章:第一印象

第3章:C++第一印象

本章会进一步探索C++。用不同的例子说明在结构体中声明函数;介绍类的概念;详细介绍类型转换;介绍几个新的类型和几个相对于C扩展的重要符号。

3.1:和C的显著差异

在开始进行”真正的“面向对象编程之前,我们先介绍一些和C编程明显的区别:不仅仅是C和C++,还有在C中没有的或者和C使用方式不同的语法结构和关键字。

3.1.1:使用const关键字


尽管const也是C语法中的一部分,但是在C++中会比C中更加常用和重要,并且严格。 const关键字是一个修饰符,表示变量或参数的值不可以被修改。下面的例子中想要改变ival变量的值,是不成功的: :: int main() { int const ival = 3; // a constant int // initialized to 3 ival = 4; // assignment produces // an error message } 这个例子演示了ival可以在定义是初始化为一个给定的值;之后尝试想更改这个值是不允许的。 相比C,用const声明的变量可以用来指定数组的大小(译注:C也可以),如下例: :: int const size = 20; char buf[size]; // 20 chars big const关键字还可以用在声明指针时,比如指针类型的参数。这样声明: :: char const *buf; buf是一个指向字符的指针。无论buf指向的是什么,它的内容都不可以通过buf来改变:字符被声明为了常量。buf指针自身是可以改变的。像*buf = 'a'这样的语句是不予寻的,但是++buf则是可以的。 像下面这样声明: :: char const *buf; buf自身是一个常量指针,不允许改变。但是它所指向的内容是可以噶变的。 最后,像这样声明: :: char const *const buf; 也是可以的。这里,无论指针自身还是其指向的内容都是不可变的。 const关键字的放置位置的规则是:无论const左边的是什么,都是不可变的。 尽管简单,这条规则却经常使用。例如Bjarne Stroustrup的表述(在 http://www.research.att.com/~bs/bs_faq2.html#constplacement ): 我应该把const放在类型的前面还是后面? 我放在前面,但这只是品味问题。“const T”和“T const”都可以并且是一样的。例如: :: const int a = 1; // OK int const b = 2; // also OK 我猜,第一种方式可能会让较少的程序员感到困惑(更地道些)。 但是我们已经见过了一个把const放在前面而产生了不期望的结果的例子,接下来就会看到。另外,放在前面的方式于const函数冲突,会在 `17.7 <chapter-7.rst#constfunctions>`_ 遇到。const函数中,const关键字放在函数名的后面,而不是前面。 定义或声明(不管有没有const)都应该从变量或函数标识往类型标识走: Buf is a const pointer to const characters 在可能会产生困惑的地方,这条规则尤其有用。In examples of C++ code published in other places one often encounters the reverse: const preceding what should not be altered. That this may result in sloppy code is indicated by our second example above: :: char const *buf; 其中哪个是常量?草草一看,指针不能改变(因为const在指针前面)。事实上,字符的值才是常量,当我们尝试编译一下下面这个程序就清楚了: :: int main() { char const *buf = "hello"; ++buf; // accepted by the compiler *buf = 'u'; // rejected by the compiler } 编译在*buf = 'u'这句失败了,而不是++buf语句。 Marshall Cline的 `C++ FAQ <http://www.parashift.com/c++-faq-lite/const-correctness.html>`_ 给了同样的规则(段落18.5),相似的问题: [18.5] “const Fred* p”和“Fred* const p”的区别是什么? 你得从右向左读这个指针的声明。 但是,Marshall Cline的建议还可以再改进下。这里有个方法,能让你不费力的解析非常复杂的声明: 1. 以读取变量的名字开始。 2. 读的足够远(向右读),直到声明结束或者一个右括号。 3. 返回到开始的地方,然后往回读(向左读),直到定义开始或者一个匹配的左括号。 4. 如果遇到了一个左括号,在你之前停止的地方重复步骤2。 让我们用这个秘诀分析下下面的声明。箭头表示每步应该读多远,箭头的方向表示向哪边读: :: char const *(* const (*(*ip)())[])[] ip Start at the variable's name: 'ip' is ip) Hitting a closing paren: revert --> (*ip) Find the matching open paren: <- 'a pointer to' (*ip)()) The next unmatched closing par: --> 'a function (not expecting arguments)' (*(*ip)()) Find the matching open paren: <- 'returning a pointer to' (*(*ip)())[]) The next closing par: --> 'an array of' (* const (*(*ip)())[]) Find the matching open paren: <-------- 'const pointers to' (* const (*(*ip)())[])[] Read until the end: -> 'an array of' char const *(* const (*(*ip)())[])[] Read backwards what's left: <----------- 'pointers to const chars' 把每步的结果结合起来,我们得到,char const \*(\* const (\*(\*ip)())[])[]意思是:ip是一个指向函数(无参数)的指针,返回一个指针,指向一个数组,数组的元素是常量指针,指针指向常量字符。这就是ip代表的意思;用这个方法,你可以解析你曾经遇到过的任何声明。 3.1.2:命名空间 ``````````````` C++引入了命名空间的概念:所有的符号都定义在一个大的上下文中,叫做命名空间。命名空间可以避免名称冲突,例如,一个程序员想要定义可以sin函数计算角度,又可以同时使用标准的sin函数计算弧度。 在第 `4 <chapter-4.rst>`_ 章中,会详细讲解命名空间。现在,你只需要知道大多数的编译器都需要显式的对标准命名空间std的声明。所以,除非另有声明,所有示例代码都在隐式的使用 :: using namespace std; 声明。所以,如果你打算编译C++注解中的示例代码,确定源文件中使用了上面的using声明。 3.1.3:域解析操作符:: ````````````````````` C++引入了几个新的操作符,其中之一就是域操作符(::)。这个操作符可以用在全局变量和局部变量同名的情况下: :: #include <stdio.h> double counter = 50; // global variable int main() { for (int counter = 1; // this refers to the counter != 10; // local variable ++counter) { printf("%d\n", ::counter // global variable / // divided by counter); // local variable } } 上面的代码中,域解析操作符用来寻址全局变量,而不是使用局部的同名变量。C++中,域解析操作符使用广泛,但是很少会遇到局部变量覆盖全局变量的情况,它的主要用途会在第 `7 <chapter-7.rst>`_ 章中讲到。 3.1.4:cout,cin和cerr

和C类似,C++定义了标准输入和标准输出流,供应用程序使用。这些流是:

  • cout,类似于stdout
  • cin,类似于stdin
  • cerr,类似于stderr

语法上,这些流不是用在函数上:而是分别使用插入操作符<<和取出操作符>>将数据写入到流或者从流读取数据。如下面的示例:

::

#include

using namespace std;

int main()
{
int ival;
char sval[30];

  cout << "Enter a number:\n";
  cin >> ival;
  cout << "And now a string:\n";
  cin >> sval;

  cout << "The number is: " << ival << "\n"
          "And the string is: " << sval << '\n';

}

程序从cin流(通常是键盘)中读取一个数字和一个字符串,然后打印这些数据到cout中。关于流,请注意:

  • 标准流是在iostream头文件中声明的。在C++注解的示例中,这个头文件经常没有明确的指出。当要使用这些流的时候,必须要包含进来。相对于使用using namespace std;语句,期望包含#include 来在示例中使用标准流。

  • cin,cout和cerr是类类型的变量。这样的变量通常称为对象。类在C++中广泛使用,第 7 <chapter-7.rst>_ 章中详细讲解。

  • cin使用取出操作符>>从一个流里取数据,然后拷贝这些数据到变量中(如上例的ival)。后面我们会讲解C++中的操作符如何执行不同的动作,以及C++给他们定义了什么行为。我们已经提到了函数重载,C++操作符可以有多个定义,叫做操作符重载。

  • cin,cout和cerr的操作符可以操作不同类型的变量。上面的例子cout << ival是打印一个证书的值,而cout << “Enter a number”是打印一个字符串。因此,操作符的动作由变量的类型决定。

  • 取出操作符(>>)通过在文本流中取数据,对变量执行类型安全赋值。通常,取值操作符会跳过要取的值前的所有空白字符。

  • 特殊符号用在特殊情形下。通常一行通过“\n”或’\n’结束。但是插入endl符号,这行数据结束,然后会冲洗内部的的缓冲。因此,通常避免使用endl,而是使用’\n’以在一定成都上提到代码的效率。

cin,cout和cerr不适C++语法的一部分。流是sotream头文件中的一部分。这和printf不属于C的语法一样,并且最初是有哪些认为功能非常重要的人编写,并将它们放入到运行时库中。

发表评论

电子邮件地址不会被公开。 必填项已用*标注