谭浩强C++ 读书笔记

C++ 程序设计
 

汇编语言 / 计算机语言
FORTARAN 语言
BASIC 语言
C 语言
C++ 语言
世界上第一种计算机高级语言
 
C++ 是由 C 发展而来的,与 C 兼容。 C++ 保留了 C 语言原有的所有优点,增加了面向对象的机制。
 
 

 
C++ 程序的编写和实现
l          C++ 语言编写程序
C++ 的源程序是以 .cpp 作为后缀的 (cpp c plus plus 的缩写 )
l          对源程序进行编译
为了使计算机能执行高级语言源程序,必须先用一种称为 编译器 (complier)” 的软件 ( 也称编译程序或编译系统 ) ,把源程序翻译成二进制形式的 目标程序 (object program)”
编译是以源程序文件为单位分别编译的。编译的作用是对源程序进行词法检查和语法检查。
l          将目标文件连接
在改正所有的错误并全部通过编译后,得到一个或多个目标文件。此时要用系统提供的 连接程序 (linker)” 将一个程序的所有目标程序
和系统的库文件以及系统提供的其他信息连接起来,最终形成一个可执行的二进制文件,它的后缀是 .exe ,是可以直接执行的。
l          运行程序
 
常量
l          整形常量
整型数据分为短整型、一般整型和长整型。
整形常量的三种表达方式:
n          十进制
n          八进制                 开头加一个数字 0 ,就表示这是以八进制数形式表示的常数。
n          二进制                 开头加一个数字 0 和一个英文字母 X( x) ,就表示这是以十六进制数形式表示的常数。
整型数据的存储方式为按二进制数形式存储。
l          浮点型常量
浮点型 ( 又称实型 ) 数据分为单精度 (float) 、双精度 (double) 和长双精度 (long double)3
浮点数的两种表示方式:
n          十进制小数形式
n          指数形式 ( 即浮点形式 )
在程序中不论把浮点数写成小数形式还是指数形式,在内存中都是以指数形式 ( 即浮点形式 ) 存储的。
在内存中都是以规范化的指数形式存放:
存储单元分为两部分,一部分用来存放数字部分,一部分用来存放指数部分。
l          字符型常量
将一个字符常量存放到内存单元时,实际上并不是把该字符本身放到内存单元中去,而是将该字符相应的ASCII代码放到存储单元中。
        
因此,在 C++ 中字符型数据和整型数据之间就可以通用。一个字符数据可以赋给一个整型变量,反之,一个整型数据也可以赋给一个字符变量。也可以对字符数据进行算术运算,此时相当于对它们的 ASCII 码进行算术运算。
l          字符串常量
l          符号常量
区别用 #define 命令定义的符号常量和用 const 定义的常变量:
符号常量只是用一个符号代替一个字符串,在预编译时把所有符号常量替换为所指定的字符串,它没有类型,在内存中并不存在以符号常量命名的存储单元。而常变量具有变量的特征,它具有类型,在内存中存在着以它命名的存储单元,可以用 sizeof 运算符测出其长度。
#define 命令定义符号常量是 C 语言所采用的方法, C++ 把它保留下来是为了和 C 兼容。 C++ 的程序员一般喜欢用 const 定义常变量。
 
强制类型转换:
l          将一个 int short long 型数据赋给一个 char 型变量,只将其低 8 位原封不动地送到 char 型变量(发生截断)。
例如
short int i=289;
char c = i;            // 将一个 int 型数据赋给一个 char 型变量
赋值情况见图 2.8 。为方便起见,以一个 int 型数据占两个字节 (16 ) 的情况来说明。
l          signed( 有符号 ) 型数据赋给长度相同的 unsigned( 无符号 ) 型变量,将存储单元内容原样照搬(连原有的符号位也作为数值一起传送)。
 
C++ 的输入与输出
输入和输出并不是 C++ 语言中的正式组成成分。 C C++ 本身都没有为输入和输出提供专门的语句结构。输入输出不是由 C++ 本身定义的,而是在编译系统提供的 I/O 库中定义的。
C++ 的输出和输入是用 ”(stream) 的方式实现的。
     
在输入流与输出流中使用控制符
双精度数
double a=123.456789012345; a 赋初值
(1) cout<<a; 输出: 123.456 
(2) cout<<setprecision(9)<<a; 输出: 123.456789 
(3) cout<<setprecision(6); 恢复默认格式 ( 精度为 6)
(4) cout<< setiosflags(ios fixed); 输出: 123.456789
(5) cout<<setiosflags(ios fixed)<<setprecision(8)<<a; 输出: 123.45678901
(6) cout<<setiosflags(ios scientific)<<a; 输出: 1.234568e+02
(7) cout<<setiosflags(ios scientific)<<setprecision(4)<<a; 输出: 1.2346e02
整数:
int b=123456; b 赋初值
(1) cout<<b; 输出: 123456
(2) cout<<hex<<b; 输出: 1e240    
(3) cout<<setiosflags(ios uppercase)<<b; 输出: 1E240    
(4) cout<<setw(10)<<b<< , <<b; 输出:      123456 123456
(5) cout<<setfill( * )<<setw(10)<<b; 输出: **** 123456
(6) cout<<setiosflags(ios showpos)<<b; 输出: +123456
 
函数:
l          函数型参:它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,表示它们并不是实际存在的数据,只有在发生函数调
用时,函数中的形参才被分配内存单元,以便接收从实参传来的数据。在调用结束后,形参所占的内存单元也被释放。
l          函数声明:就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,以便使编译能正常进行。
l          内置函数:在编译时将所调用函数的代码直接嵌入到主调函数中,而不是将流程转出去。
使用内置函数可以节省运行时间,但却增加了目标程序的长度。因此一般只将规模很小而使用频繁的函数声明为内置函数。
l          函数重载:用同一函数名定义多个函数,这些函数的参数个数和参数类型不同。
l          函数模板:建立一个通用函数,其函数类型和形参类型不具体指定。
定义函数模板的一般形式为
template < typename T>   template <class T>
通用函数定义
实例:
         template<typename T>                  //       模板声明,其中 T 为类型参数
T max(T a,T b,T c)                        //       定义一个通用函数,用 T 作虚拟的类型名
{
if(b>a)
a=b;
 if(c>a)
a=c;
       
return a;
}
 
int main( )
{
int i1=185,          i2=-76,       i3=567,i;
double d1=56.87,         d2=90.23, d3=-3214.78,d;
long g1=67854,   g2=-912456,        g3=673456,g;
 
i=max(i1,i2,i3);                      //       调用模板函数,此时 T int 取代
        d=max(d1,d2,d3);                  //       调用模板函数,此时 T double 取代
g=max(g1,g2,g3);                   //       调用模板函数,此时 T long 取代
 
return 0;
}
 
l          全局变量
全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被屏蔽,即它不起作用。
        
变量的两个属性:
l          作用域:
n          文件作用域 (file scope)
n          函数作用域 (function scope)
n          块作用域 (block scope)
n          函数原型作用域 (function prototype scope)
l          存储类别
n          自动的 (auto)
n          静态的 (static)
n          寄存器的 (register)
在程序中定义寄存器变量对编译系统只是建议性 ( 而不是强制性 ) 的。当今的优化编译系统能够识别使用频繁的变量,自动地将这些变量放在寄存器中。
n          外部的 (extern)
全局变量可以为本文件中各个函数所引用。编译时将全局变量分配在静态存储区。有时需要用 extern 来声明全局变量,以扩展全局变量的作用域。
u        在一个文件内声明全局变量
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作外部变量声明,表示该变量是一个将在下面定义的全局变量。有了此声明,就可以从声明处起,合法地引用该全局变量,这种声明称为提前引用声明。
u        在多文件的程序中声明外部变量
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量 num ,不能分别在两个文件中各自定义一个外部变量 num 。正确的做法是:在任一个文件中定义外部变量 num ,而在另一文件中用 extern num 作外部变量声明。即
extern int num;
编译系统由此知道 num 是一个已在别处定义的外部变量,它先在本文件中找有无外部变量 num ,如果有,则将其作用域扩展到本行开始 ( 如上节所述 ) ,如果本文件中无此外部变量,则在程序连接时从其他文件中找有无外部变量 num ,如果有,则把在另一文件中定义的外部变量 num 的作用域扩展到本文件,在本文件中可以合法地引用该外部变量 num
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个 static 声明。
 
         函数本身也分为内部的和外部的,定义方式和变量完全相同。
默认情况下,函数是全局的,在定义内部函数时 , 在函数名和函数类型的前面加 static ,以表明该函数只能在本文件中使用,从而可以变相的屏蔽掉多个文件中的同名函数。
 
静态变量和动态变量的比较:
n          静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,存储在动态存储区空间 ( 而不是静态存储区空间 ) ,函数调用结束后即释放。
n          为静态局部变量赋初值是在编译时进行值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而为自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句
n          如果在定义局部变量时不赋初值的话,对静态局部变量来说,编译时自动赋初值 0( 对数值型变量 ) 或空字符 ( 对字符型变量 ) 。而对自动变量来说,如果不赋初值,则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
n          虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的,也就是说,在其他函数中它是 不可见 的。
 
内存中的供用户使用的存储空间
(1) 程序区
(2) 静态存储区
(3) 动态存储区
数据分别存放在静态存储区和动态存储区中。
全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储单元,程序执行完毕就释放这些空间。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。
在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,则要进行两次分配和释放,而两次分配给此函数中局部变量的存储空间地址可能是不相同的。
一般情况下,变量的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到 CPU 中的运算器。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
变量作用域和生存期的一个图形:
             
指针:
一般的 C++ 编译系统为每一个指针变量分配 4 个字节的存储单元,用来存放变量的地址。
l          变量存取方式:
在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。
n          直接存取方式:按变量地址存取变量值的方式
n          间接存取方式:在程序中定义这样一种特殊的变量,它是专门用来存放地址的。
l          两个与指针变量有关的运算符
n          &取地址运算符。
n          * 指针运算符(或称间接访问运算符)
l          指针和数组
一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。
C++ 中,数组名代表数组中第一个元素 ( 即序号为 0 的元素 ) 的地址。
l          多维数组和指针
 
l          函数与指针
n          用函数指针变量调用函数
指针变量也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
实例:
#include <iostream>
using namespace std;
 
int main( )
{
int max(int x,int y);              //    函数声明
int (*p)(int,int);                 //    定义指向函数的指针变量 p
int a,b,m;
p=max;                        //   使 p 指向函数 max
cin>>a>>b;
m=p(a,b);
cout<< max= <<m<<endl;
return 0;
}
 
int max(int x,int y)
{
int z;
if(x>y)
z=x;
else
z=y;
return(z);
}
 
n          用指向函数的指针作函数参数
函数指针变量常见的用途之一是作为函数的参数,将函数名传给其他函数的形参。这样就可以在调用一个函数的过程中根据给定的不同实参调用不同的函数。
n          指针数组
n          指向指针的指针
 
引用
l          这是 C++ C 的一个重要扩充,引用是一种新的变量类型,它的作用是为一个变量起一个别名。
l          声明变量 b 为引用类型,并不需要另外开辟内存单元来存放 b 的值。 b a 占内存中的同一个存储单元,它们具有同一地址。
l          在声明一个引用类型变量时,必须同时使之初始化,即声明它代表哪一个变量。在声明变量 b 是变量 a 的引用后,在它们所在函数执行期间,该引用类型变量 b 始终与其代表的变量 a 相联系,不能再作为其他变量的引用 ( 别名 )
l          引用作为函数参数时,和指针的比较:
n          指针变量要另外开辟内存单元,其内容是地址。而引用变量不是一个独立的变量,不单独占内存单元
 
自定义数据类型
l          结构体类型
l          共用体
使几种不同类型的变量存放到同一段内存单元中。
以上 3 个变量在内存中占的字节数不同,但都从同一地址开始存放。也就是使用覆盖技术,几个变量互相覆盖。
n          使用共用体变量的目的是希望用同一个内存段存放几种不同类型的数据。但请注意: 在每一瞬时只能存放其中一种,而不是同时存放几种。
n          能够访问的是共用体变量中最后一次被赋值的成员,在对一个新的成员赋值后原有的成员就失去作用。
n          共用体变量的地址和它的各成员的地址都是同一地址。
 
l          枚举类型
l          typedef 声明类型
typedef 声明一个新的类型名来代替已有的类型名。
typedef 只是对已经存在的类型增加一个类型名,而没有创造新的类型。
 
 
动态分配和撤销内存的运算符
C 语言中是利用库函数 malloc free 来分配和撤销内存空间的。 C++ 提供了较简便而功能较强的运算符 new delete 来取代 malloc free 函数。
 
 
1           第一章
2           第二章
3           第三章
4           第四章
5           第五章
6           第六章
7           第七章
8           第八章
9           第九章
10        第十章
11        第十一章
12        第十二章
13        第十三章
14        第十四章
14.1         异常处理
C++ 发展的后期,有时 C++ 编译系统根据实际工作的需要,增加了一些功能,作为工具来使用,其中主要有模板 ( 包括函数模板和类模板 ) 、异常处理、命名空间和运行时类型识别,以帮助程序设计人员更方便地进行程序的设计和调试工作。 1997 ANSI C++ 委员会将它们纳入了 ANSI C++ 标准,建议所有的 C++ 编译系统都能实现这些功能。这些工具是非常有用的, C++ 的使用者应当尽量使用这些工具。由此可见,异常处理本身并不是 C++ 的组成部分,而是编译器提供的功能。
C++ 采取的办法是 : 如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级 ( 即调用它的函数 ) ,它的上级捕捉到这个信息后进行处理。如果上一级的函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。这样做使异常的发现与处理不由同一函数来完成。好处是使底层的函数专门用于解决实际任务,而不必再承担处理异常的任务,以减轻底层函数的负担,而把处理异常的任务上移到某一层去处理。
14.1.1    C++ 处理异常的机制
C++ 处理异常的机制是由 3 个部分组成的,即检查 (try) 、抛出 (throw) 和捕捉 (catch) 。把需要检查的语句放在 try 块中, throw 用来当出现异常时发出一个异常信息,而 catch 则用来捕捉异常信息,如果捕捉到了异常信息,就处理它。在进行异常处理后,程序并不会自动终止,继续执行 catch 子句后面的语句。
try 块和 catch 块作为一个整体出现, catch 块是 try-catch 结构中的一部分,必须紧跟在 try 块之后,不能单独使用,在二者之间也不能插入其他语句。但是在一个 try-catch 结构中,可以只有 try 块而无 catch 块。即在本函数中只检查而不处理,把 catch 处理块放在其他函数中。
try catch 块中必须有用花括号括起来的复合语句,即使花括号内只有一个语句,也不能省略花括号。
一个 try-catch 结构中只能有一个 try 块,但却可以有多个 catch 块,以便与不同的异常信息匹配。
如果在 catch 子句中没有指定异常信息的类型,而用了删节号 “…” ,则表示它可以捕捉任何类型的异常信息,
如:
        catch(…)
{
cout<< OK <<endl;
}
它能捕捉所有类型的异常信息,并输出″ OK ″。
如果 throw 抛出的异常信息找不到与之匹配的 catch 块,那么系统就会调用一个系统函数 terminate ,使程序终止运行。
14.1.2    在函数声明中进行异常情况指定
为便于阅读程序,使用户在看程序时能够知道所用的函数是否会抛出异常信息以及异常信息可能的类型, C++ 允许在声明函数时列出可能抛出的异常类型,
如:
double triangle(double,double,double) throw(double);
表示 triangle 函数只能抛出 double 类型的异常信息。如果写成
 
double triangle(double,double,double) throw(int,double,float,char);
则表示 triangle 函数可以抛出 int,double,float char 类型的异常信息。
 
异常指定是函数声明的一部分,必须同时出现在函数声明和函数定义的首行中,否则在进行函数的另一次声明时,编译系统会报告 类型不匹配
如果在声明函数时未列出可能抛出的异常类型,则该函数可以抛出任何类型的异常信息。
如果想声明一个不能抛出异常的函数,可以写成以下形式 :
double triangle(double,double,double) throw();//throw 无参数
这时即使在函数执行过程中出现了 throw 语句,实际上也并不执行 throw 语句,并不抛出任何异常信息,程序将非正常终止。
14.1.3    在异常处理中处理析构函数
如果在 try ( try 块中调用的函数 ) 中定义了类对象,在建立该对象时要调用构造函数。在执行 try ( 包括在 try 块中调
用其他函数 ) 的过程中如果发生了异常,此时流程立即离开 try 块。这样流程就有可能离开该对象的作用域而转到其他函数,因而应当事先做好结束对象前的清理工作, C++ 的异常处理机制会在 throw 抛出异常信息被 catch 捕获时,对有关的局部对象进行析构 ( 调用类对象的析构函数 ) 析构对象的顺序与构造的顺序相反,然后执行与异常信息匹配的 catch 块中的语句。
 
14.2         命名空间
命名空间是 ANSI C++ 引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。在 C 语言中定义了 3 个层次的作用域,即文件 ( 编译单元 ) 、函数和复合语句。 C++ 又引入了类作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不干扰,系统能够区别它们。
#include 命令行将这些头文件包含进来。由于各头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来
命名所定义的类或函数。这样在程序中就会出现名字冲突。在预编译后,头文件中的内容取代了对应的 #include 命令行 ,这样就在同一个程序文件中出现了两个 Student 类和两个 fun 函数,显然是重复定义,这就是名字冲突,即在同一个作用域中有两个或多个同名的实体。
C 语言和早期的 C++ 语言没有提供有效的机制来解决这个问题,没有使库的提供者能够建立自己的命名空间的工具。人们希望 ANSI C++ 标准能够解决这个问题,提供一种机制、一种工具,使由库的设计者命名的全局标识符能够和程序的全局实体名以及其他库的全局标识符区别开来。
为了解决上面这个问题, ANSI C++ 增加了命名空间 (namespace) 。所谓命名空间,实际上就是一个由程序设计者命名的内存区域。程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。
14.2.1    命名空间的定义
在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型 :
l          变量 ( 可以带有初始化 )
l          常量;
l          函数 ( 可以是定义或声明 )
l          结构体;
l          类;
l          模板;
l          命名空间 ( 在一个命名空间中又定义一个命名空间,即嵌套的命名空间 )
例如:
namespace ns1
{
const int RATE=0.08;                              // 常量
double pay;                                  // 变量
double tax( )                              // 函数
{
return a*RATE;
}
namespace ns2                     // 嵌套的命名空间
{
int age;
}
}
如果想输出命名空间 ns1 中成员的数据,可以采用下面的方法 :
cout<<ns1::RATE<<endl;
cout<<ns1::pay<<endl;
cout<<ns1::tax()<<endl;
cout<<ns1::ns2::age<<endl;                              // 需要指定外层的和内层的命名空间名
14.2.2    使用命名空间成员的方法
14.2.2.1    使用命名空间别名
可以为命名空间起一个别名 (namespace alias) ,用来代替较长的命名空间名。
namespace Television                     //       声明命名空间,名为 Television
        {…}
可以用一个较短而易记的别名代替它。如
namespace TV = Television;                       //       别名 TV 与原名 Television 等价
 
14.2.2.2    使用 using 命名空间成员名
14.2.2.3    使用 using namespace 命名空间名
14.2.3    标准命名空间 std
14.3         使用早期的函数库
C 语言程序中各种功能基本上都是由函数来实现的,在 C 语言的发展过程中建立了功能丰富的函数库, C++ C 语言继承了这份宝贵的财富。在 C++ 程序中可以使用 C 语言的函数库。如果要用函数库中的函数,就必须在程序文件中包含有关的头文件,在不同的头文件中,包含了不同的函数的声明。
         C++ 中使用这些头文件有两种方法。
l          C 语言的传统方法。头文件名包括后缀 .h ,如 stdio.h math.h 等。由于 C 语言没有命名空间,头文件并不存放在命名空间中,因此在 C++ 程序文件中如果用到带后缀 .h 的头文件时,不必用命名空间。只需在文件中包含所用的头文件即可。如
#include <math.h>
l          C++ 的新方法。 C++ 标准要求系统提供的头文件不包括后缀 .h ,例如 iostream string 。为了表示与 C 语言的头文件有联系又有区别, C++ 所用的头文件名是在 C 语言的相应的头文件名 ( 但不包括后缀 .h) 之前加一字母 c
此外,由于这些函数都是在命名空间 std 中声明的,因此在程序中要对命名空间 std 作声明。
                   如:
#include <cstdio>
#include <cmath>
using namespace std;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值