您的位置:首页 > 编程语言 > C语言/C++

c++ primer 3rd edition 学习笔记(一)

2007-04-20 02:07 260 查看
第一篇 c++概述
在过程化程序设计方法(procedural programming)中一个问题可直接由一组算法来建立模型,数据被独立存储起来,我们既可以在某个全局位置上访问这些数据或者把数据传递给过程以便它能够访问这些数据。Fortran,C和Pascal是三种著名的过程语言,C++也支持过程化程序设计。

在20世纪70 年代程序设计的焦点从过程化程序设计方法转移到了抽象数据类型abstract data type 简写为ADT的程序设计上,现在通常称之为基于对象(object based的程序设计,在基于对象的程序设计方法中我们通过一组数据抽象来建立问题的模型,在C++中我们把这些抽象称为类(class),与每个类相关的算法被称为该类的公有接口(public interface)。数据以私有形式被存储在每个对象中,对数据的访问应与一般的程序代码隔离开来。CLU,Ada和Modula-2 是三种支持抽象数据类型的程序设计语言。

面向对象的程序设计方法通过继承(inheritance)机制和动态绑定(dynamic binding)机制扩展了抽象数据类型,继承机制是对现有实现代码的重用,动态绑定是指对现有的公有接口的重用。以前独立的类型现在有了类型/子类型的特定关系。Simula,Smalltalk 和Java 是三种支持面向对象程序设计方法的著名语言。

C++是一种支持多种程序设计方法的语言,虽然我们主要把它当作面向对象的语言,但实际上它也提供对过程化的和基于对象的程序设计方法的支持,这样做的好处是对每个问题都能够提供最合适的解决方案,事实上没有一种程序设计方法能够对所有的问题都提供最好的解决方案,这样做带来的缺点是使得语言过于庞大复杂。

1、开始
解决大问题方法:分而治之divide and conquer和逐步求精stepwise refinement。
标准C++中如果main()函数没有显式地提供返回语句则它缺省返回0。

标准C++头文件没有后缀这是个例外。
补充解释:准确地说是C++标准头文件写在#include中时都没有后缀。
C++标准头文件包括:
<algorithm> <bitset> <complex> <deque> <exception> <fstream> <functional> <iomanip> <ios> <iosfwd> <iostream> <istream> <iterator> <limits> <list> <locale> <map> <memory> <new> <numeric> <ostream> <queue> <set> <sstream> <stack> <stdexcept> <streambuf> <string> <typeinfo> <utility> <valarray> <vector>
<cassert> <cctype> <cerrno> <cfloat> <ciso646> <climits> <clocale> <cmath> <csetjmp> <csignal> <cstdarg> <cstddef> <cstdio> <cstdlib> <cstring> <ctime> <cwchar> <cwctype>
要习惯于使用这些头文件来代替旧的非标准的头文件。

C++程序文件的后缀在不同的C++实现产品中是不同的,头文件的后缀在C++的不同实现中也不相同,这也是标准C++没有指定头文件后缀的一个原因。

using namespace std;
C++标准库中的名字都是在一个称作std的名字空间中声明的,这些名字是不可见的除非我们显式地使它们可见。

#include
如果文件名用尖括号<和> 括起来表明这个文件是一个工程或标准头文件,查找过程会检查预定义的目录,我们可以通过设置搜索路径环境变量或命令行选项来修改这些目录。如果文件名用一对引号括起来则表明该文件是用户提供的头文件,查找该文件时将从当前文件目录开始。被包含的文件还可以含有#include指示符,由于嵌套包含文件的原因一个头文件可能会被多次包含在一个源文件中,条件指示符可防止这种头文件的重复处理,例如


#ifndef BOOKSTORE_H


#define BOOKSTORE_H




/**//* Bookstore.h 的内容 */


#endif

这里BOOKSTORE_H是一个预编译器常量,习惯上预编译器常量往往被写成大写字母。

编译C++程序时编译器自动定义了一个预处理器名字__cplusplus,注意前面有两个下划线,因此我们可以根据它来判断该程序是否是C++程序,以便有条件地包含一些代码。在编译标准C时编译器将自动定义名字__STDC__,当然__cplusplus与__STDC__不会同时被定义。另外两个比较有用的预定义名字是__LINE__和__FILE__。__LINE__记录文件已经被编译的行数,__FILE__包含正在被编译的文件的名字。另外两个预定义名字分别包含当前被编译文件的编译时间__TIME__ 和日期__DATE__。

assert()是C语言标准库中提供的一个通用预处理器宏,判断一个必需的前提条件。为了使用assert() 必须包含与之相关联的头文件#include <assert.h>。这个头文件的C++名字是cassert。C库头文件的C++名字总是以字母C开头后面是去掉后缀.h的C名字。使用C++名字时必须使用using指示符。

注释对不能嵌套。*和/之间有空格不被看作注释符。

问题:这段程序在vc6里无法结束~


#include <iostream>


#include <string>


using namespace std;


int main()




...{


string word;


while(cin>>word)


cout<<"word read is:"<<word<<' ';


cout<<"ok: no more words to read: bye! ";


return 0;


}

iostream库的文件输入输出例子


#include <iostream>


#include <fstream>


#include <string>


using namespace std;


int main()




...{


ofstream outfile("out_file.txt");


ifstream infile("in_file.txt");




if(!infile)...{


cerr<<"error: unable to open input file! ";


return -1;


}




if (!outfile)...{


cerr<<"error: unable to open output file! ";


return -2;


}


string word;


while (infile>>word)


outfile<<word<<endl;


return 0;


}

2、浏览
内建数组类型不支持赋值操作因为:数组名其实代表一个常量指针。
int ival =1024;
指示编译器分配足够的存储区以存放一个整型值,该存储区与名字ival相关联然后用数值1024初始化该存储区.
C++支持用指针类型来存放对象的内存地址值
int *pint;
C++预定义了一个专门的取地址address-of操作符&.当我们把它应用在一个对象上时,返回的是对象的地址值
pint = &ival; // 把ival的地址pint
为了访问pint所指向的实际对象我们必须先用解引用dereference操作符*来解除pint的引用
*pint = *pint + 1;
它等价于
ival = ival + 1;

静态与动态内存分配的两个主要区别是
1.静态对象是有名字的变量,我们直接对其进行操作,而动态对象是没有名字的变量,我们通过指针间接地对它进行操作
2.静态对象的分配与释放由编译器自动处理,程序员需要理解这一点,但不需要做任何事,动态对象的分配与释放必须由程序员显式地管理,

通过new 和delete 两个表达式来完成
对象的动态分配可通过new表达式的两个版本之一来完成
第一个版本用于分配特定类型的单个对象例如
int *pint = new int(1024);
分配了一个没有名字的int类型的对象,对象初始值为1024,然后表达式返回对象在内存中的地址,接着这个地址被用来初始化指针对象pint,

对于动态分配的内存惟一的访问方式是通过指针间接地访问。
第二个版本用于分配特定类型和维数的数组例如
int *pia = new int[4];
分配了一个含有四个整数元素的数组,不幸的是我们没有办法给动态分配的数组的每个元素显式地指定一个初始值。分配动态数组时一个常令人迷惑的问题是返回值只是一个指针,与分配单一动态对象的返回类型相同,pint与pia的不同之处在于,pia拥有四元素数组的第一个元素的地址,而pint只是简单地包含单一对象的地址。
当用完了动态分配的对象或对象的数组时我们必须显式地释放这些内存。我们可以通过使用delete 表达式的两个版本之一来完成使用单一对象的delete表达式形式如下
// 删除单个对象
delete pint;
数组形式的delete表达式如下
// 删除一个对象数组
delete [] pia;
如果忘了删除动态分配的内存又会怎么样呢?如果真的如此程序就会在结束时出现内存泄漏memory leak的问题,内存泄漏是指一块动态分配的内存我们不再拥有指向这块内存的指针,因此我们没有办法将它返还给程序供以后重新使用。

两个成员访问操作符member access operator
用于类对象的点操作符.
用于类对象指针的箭头操作符->

出现在类体中公有public部分的成员在一般程序的任何地方都可以访问,出现在私有private 部分的成员只能在该类的成员函数或友元friend被访问。

由于C++不允许成员函数与数据成员共享同一个名字,所以在这样的情况下一般的习惯是在数据成员名字前面加一个下划线_。

一般来说函数调用比直接访问内存的开销要大得多因而信息隐藏是否给程序的执行效率增加了严重的额外负担呢。在一般情况下回答是不C++提供的解决方案是内联函数inline function机制,内联函数在它的调用点上被展开,一般来说内联函数不会引入任何函数调用,函数调用在编译期间被内联扩展为对成员变量的访问。在类定义中被定义的成员函数会被自动当作是内联函数,此外我们也可以用inline关键字显式地要求一个函数被视为内联函数。

构造函数是一种特殊的类成员函数,专门用于初始化对象,如果构造函数被定义了那么在类的每个对象第一次被使用之前,这构造函数就被自动应用在对象上。构造函数由我们来定义,为一个类确定必要的构造函数是程序设计不可缺少的一部分。
为了定义一个构造函数,我们只要给它与类相同的名字即可,另外我们不能给构造函数指定返回值,但是可以给类定义多个构造函数,尽管它们都具有相同的名字,但只要编译器能够根据参数表区分它们就行。
缺省构造函数default constructor,不需要用户提供任何参数

C++支持被称为函数重载function overloading 的机制函数重载允许两个或更多个函数使用同一个名字限制条件是它们的参数表必须不同双冒号::操作符被称为域操作符scope operator,当与一个类名相连的时候就成为一个类域操作符,我们可以非正式地把域看作是一个可视窗口,全局域的对象在它被定义的整个文件里一直到文件末尾都是可见的,在一个函数内被定义的对象是局域的local scope,它只在定义其的函数体内可见,每个类维持一个域在这个域之外它的成员是不可见的,类域操作符告诉编译器后面的标识符可在该类的范围内被找到。

引用reference是一种没有指针语法的指针,例如,IntArray &rhs,因此我们写成rhs._size 而不是rhs->_size,与指针一样引用提供对对象的间接访问

类机制还支持特殊的析构成员函数destructor member function,每个类对象在被程序最后一次使用之后,它的析构函数就会被自动调用,我们通过在类的名字前面加一个波浪线~来标识析构函数,一般地析构函数会释放在类对象使用和构造过程中所获得的资源。

构造函数和析构函数是程序员提供的函数,它们既不构造也不销毁类的对象,编译器自动把它们作用到这些对象上。类的构造函数主要用来初始化类对象的数据成员,析构函数主要负责释放类对象在生命期内申请到的所有资源。

在C++中继承由虚拟函数virtual function机制来自动完成,基类的类型相关的成员函数声明为virtual
在类的保护区域内的数据成员和成员函数不提供给一般的程序只提供给派生类放在基类的私有区域内的成员只能供该类自己使用派生类不能使用下,它的算法由特定的基类或派生类的行为或实现来决定。

对于一个非虚拟函数的调用,编译器在编译时刻选择被调用的函数,而虚拟函数调用的决定则要等到运行时刻,在执行程序内部的每个调用点上系统根据被调用对象的实际基类或派生类的类型来决定选择哪一个虚拟函数实例。虚拟函数比非虚拟函数的效率要低一些,因为它们不能被内联,内联发生在编译时刻而虚拟函数是在运行时刻被处理的,所以它们可能是运行时刻效率低下的原因之一。

class IntArrayRC : public IntArray
冒号定义了IntArrayRC 是从IntArray 派生而来的关键字public表明派生类共享基类的公有接口IntArrayRC 类型的对象可以用在任何可以使用基类类型对象的位置上。

派生类对象的初始化过程是这样的,首先自动调用每个基类的构造函数来初始化相关的基类子对象,然后再执行派生类的构造函数。从设计的角度来看,派生类的构造函数应该只初始化那些在派生类中被定义的数据成员而不是某类中的数据成员。

inline IntArrayRC::IntArrayRC( const int *iar, int sz )
: IntArray( iar, sz ) {}
由冒号分割出来的部分称作成员初始化列表member initialization list 它提供了一种机制使我们可以向IntArray 的构造函数传递参数

C++支持另外两种形式的继承,多重继承multiple inheritance,也就是一个类可以从两个或多个基类派生而来,以及虚拟继承virtual inheritance 在这种继承方式下基类的单个实例在多个派生类之间共享

template <class elemType>
关键字template引入模板参数,由一对尖括号< > 括起来——本例中有一个参数elemType,关键字class表明这个参数代表一个类型标识符,elemType代表实际的参数名,作为实际类型的占位符

2007年4月19日
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: