c++学习日志(7.8-7.15)

/*学习内容来自《c++程序设计语言》(特别版) 裘宗燕*/

一.源文件和程序

1.分别编译

    程序是由好多源文件组成的,一般来说,一个程序就是一个文件的说法是行不通的。这样

做的好处是可以让我们更好的理解程序,假如一个程序就是一个文件,那么当这个文件和他所

依赖的某东西做了修改,那么整个程序都要重新编译。

    源文件的编译是这样的,首先进行的是预处理,包括宏的替换和文件#include包含,预处

理结束后的东西就是编译单位。

    分别编译能工作的前提是,在各个源文件里必须提供各种声明,以提供一些有关程序其他部

分类型的信息,在各个源文件和头文件里,这些声明要保持一致。连接器就是干这个工作的,

他用来将各个编译单位约束在一起。

2.连接

    所有的名字空间,类,函数都应该在他们出现的各个编译单位有适当的声明,而且他们都

应该引用同一个实体。声明包括定义式声明和非定义的声明:对于一个变量x:

           int x;                      //这是一个定义

           extern int x;            //声明,非定义

           extern int x = 5;     //是定义,带有初始式的声明就是定义,等价于 int x =

5;

    在一个程序里,一个变量只能定义一次,却可以有多个非定义的声明,但要保持类型的一

致:

           //f1.c

           int x = 1;

           int y = 1;

           //f2.c

           int x;                      //错拉,重复定义

           extern double y;     //错拉,类型不一致

     一个函数在使用之前必须声明,这与c语言不同,c语言可以调用未声明的函数。
     虽然一个变量只能定义1次,但在程序里,有很多不可避免的因素,使一些类型不得不出

现1次以上的定义:书上这部分不是太懂。
     存在“单一定义规则”(ODR);
     他的意思是,一个类,摸板,抑或一个inline函数,的俩个定义能够被接受为唯一一个定

义的实例:
     1.他们出现在不同的编译单位里;
     2.他们的所有词法相同;
     3.他们的词所代表的意义也相同;
ODR的存在是因为:
     //flie s.h
          struct S { int a;  char b; }
          void f(S*);
     //file1.c
          #include "s.h"
          //使用f()
     //file2.c
          #include "s.h"
          void f(S* p) { /* ... */ }  //f的实现
这样就让S的定义既出现在file1.c里和file2.c里成为理所当然的事了。
对上面的第3点出错的可能最大,所以要把头文件做的够强,把该有的名字都声明,抑或定义一

下。

     假如一个名字可以在与其定义所在不同的编译单位里使用,就是它具有外部连接,否则就

是具有内部连接:
     inline函数必须在需要用它的每个编译单位用完全一样的代码定义一遍。也就是说,

inline函数像名字空间一样,不存在不是定义的声明。
     const,typedef在默认的情况下具有内部连接:
            //f1.c
            const int x = 7;
            //f2.c
            const int x = 8;        //完全可以,内部连接就是互不干扰
     这样的东西会造成一些混乱,所以把他们放进头文件吧。
     当然,可以去掉这种默认,使const具有外部连接:
            //f1.c
            extern const int a = 77;
            //f2.c
            extern const int a;            // 这个a就是f1.c里的a
     无名名字空间效果很像内部连接。
     另外,static也表示使用内部连接。具体用法不清楚。

3.头文件
     #include "haha.h"的意思就是,用文件haha.h的内容取代#include所在的这一行。
     头文件里可以包括的东西:

命名名字空间          namespace N {/* ...*/}

类型定义                  struct Point { int x,y; };

模板声明                  template <class T> class z;

模板定义                  template <class T> class v {/*...*/};

函数声明                  extern int strlen( const char* );

在线函数定义          inline char get( char* p ) { return *p++; }

数据声明                  extern int a;

常量定义                  const float pi = 3.141593;

枚举                          enum Light { red, yellow, green };

名字声明                  class Matrix;

包含指令                  #include <algorithm>

宏定义                      #define VERSION 12

条件编译指令          #ifdef  __cplusplus

注释                          /* check for end of file*/

 


     头文件里不该有的东西:

常规的函数定义      char get( char* p ) { return *p++; }

数据定义                  int a;

聚集量定义              short tbl[] = { 1,2,3 };

无名名字空间          namespace {/*...*/}

导出的模板定义      export template <class T> f( T t ) {/*...*/}
     标准库头文件,是由系统提供的编程工具。
4.非c++代码的连接
     extern "C" char* strcpy(char*, const char*);
     extern char* strcpy(char*, const char*);
     以上俩段的区别就是连接约定不同。"C"表示一种约定,却不是语言。
     连接约定不影响调用函数的语意。
     为一组声明描述连接约定的语法:
           extern "C"{
                /*声明*/
           }
     这种结构被叫做连接块,可以用来包裹起整个的c头文件,使之适合c++:
           extern "C"{
           #include <string.h>
           }
     还有一种条件编译的方式
     用连接块包裹的变量,其作用域和存储类不会受到影响
           extern "C" {
                 int g1;     //是定义
           }
           extern "C" int g3; //声明,却不是定义
     具有c连接的库,包含到选定的名字空间,不去污染全局名字空间是什么意思?
c++实体的连接必须将名字空间考虑在内,那么c的连接不用考虑名字空间?于是,c就能包含到

选定的名字空间?
函数指针情况:
typedef int (*FT) ( const void*, const void* );   //FT具有C++连接
void isort( void* p, size_t n, size_t sz, FT cmp ); //cmp具有C++连接

extern "C" {
 typedef int (*CFT) ( const void*, const void* );  //CFT具有C连接
 void qsort( void* p, size_t n, size_t sz, CFT cmp ); //qsort具有C连接,cmp 

                                                           //具有C连接
}


void xsort( void* p, size_t n, size_t sz, CFT cmp ); //cmp具有C连接,xsort是C++连接
extern "C" void ysort( void* p, size_t n, size_t sz, FT cmp ); //cmp具有C++连接,  

                                                            //ysort是C连接

int compare( const void*, const void* );   //compare具有C++连接
extern "C" int ccmp( const void*, const void* );  //ccmp具有C连接

void f( char* v, int sz )
{
 qsort( v, sz, 1, &compare );  //具有C++连接的函数作参数,传递到本应是C连接 

                                    //的形参,error
 qsort( v, sz, 1, &ccmp );     //具有C连接的函数作参数,传递到C连接的形参, 

                                    //ok

 isort( v, sz, 1, &compare );  //具有C++连接的函数作参数,传递到C++连接的形 

                                    //参,ok
 isort( v, sz, 1, &ccmp )      //具有C连接的函数作参数,传递到本应是C++连接 

                                    //的形参,error
}
在为一个声明刻画连接约定时,这个约定将应用于此声明引进的所有函数类型,函数名,变量

名。对这句的理解是:CFT具有c连接,由CFT引进的cmp也具有了C连接了。上面的error不一定

是error,只是要当心。
5.头文件的组织
   有俩种方式:单一头文件和多头文件:
   桌面计算器程序的俩种组织方式。
   单一头文件的完整代码:

//dc.h
/************************************************************************
                              单头文件
*************************************************************************/
/*有关异常的名字空间*/
namespace Error {
 struct zero_divide { };            //除0错误
    struct syntax_error {              //语法错误
  const char* p;
  syntax_error( const char* q ) { p = q; }
 };
}
/*有关词法分析的名字空间*/
#include 
 
 
  
  
namespace Lexer {
 enum Token_value {                 //词法符号:数字,名字,运算符等         

  
  NUMBER,        NAME,        END,
        PLUS = '+',    MINUS = '-', MUL = '*',   DIV = '/',
  LP = '(',      RP = ')',    PRINT = ';', ASSIGN = '='
 };
 extern double number_value;        //声明,存储数字的变量
 extern std::string string_value;   //声明,存储名字的变量
 extern Token_value curr_tok;       //声明,当前的词法符号
 Token_value get_token();           //声明,取得curr_tok的函数
}
/*有关语法分析的名字空间*/
namespace Parser {
 double prim( bool get );           //声明,处理初等项
 double term( bool get );           //声明,处理乘除
 double expr( bool get );           //声明,处理加减
            
}
/*有关符号表的声明*/
#include 
  
  
extern std::map
   
   
    
     table; //声明,一个〈字符串,值〉对
/*有关驱动的一些声明所属的名字空间*/
namespace Driver {
 void skip();                       //声明,当出错时跳跃到PRINT的函数
 extern int no_of_errors;           //声明,最终程序返回给系统的值
}
//lexer.cpp
/*********************************************************************
                 词法分析的定义
*********************************************************************/
#include "dc.h"
#include 
    
    
     
     
using std::cin;
#include 
     
     
      
      

Lexer::Token_value Lexer::curr_tok;
double Lexer::number_value;
std::string Lexer::string_value;

Lexer::Token_value Lexer::get_token()             //词法分析
{
 char ch;
 do{                                           //忽略除/n以外的空白单词
  if( !cin.get(ch) ) return curr_tok = END;
 }while( ch != '/n' && isspace( ch ) );
 switch( ch ) {
 case '/n':                                    //可以用;或'/n'结束一个表达

式
 case ';':
  return curr_tok = PRINT;
 case '+':  case '-':  case '*':  case '/':    //处理常见的符号
    case '=':  case '(':  case ')':  
  return curr_tok = Token_value( ch );
 case '0':  case '1':  case '2':  case '3':    //处理number_value
 case '4':  case '5':  case '6':  case '7':
 case '8':  case '9':
  cin.putback( ch );
  cin >> number_value;
  return curr_tok = NUMBER;
 default:                                      //处理string_value
  if( isalpha(ch) ) {
   string_value = ch;
   while( cin.get( ch ) && isalnum( ch ) ) string_value += ch;
   cin.putback(ch);                      //这一句很重要,使得  

                                                            //这个ch可被下一次      

                                                        //get_token用到
   return curr_tok = NAME;
  }
  throw Error::syntax_error( "非法单词!" );
 }
}
//parser.cpp
/************************************************************************
                   语法处理
************************************************************************/
#include "dc.h"
using namespace Lexer;                   //为了偷懒的使用词法分析里的名字
/*处理初等项,初等项里的词法层次最低,在用赋值和括号时却要调用高层次的expr*/
double Parser::prim( bool get )
{
 if( get ) get_token();
 switch( curr_tok ) {
 case NUMBER:
 {
  double v = number_value;
  get_token();
  return v;
 }
 case NAME: 
 {
  double& v = table[ string_value ];
  if( get_token() == ASSIGN ) v = expr( true );
  return v;
 }
 case LP: 
 {
  double e = expr( true );
  if( curr_tok != RP ) throw Error::syntax_error( "缺少右)号" );
  get_token();
  return e;
 }
 case MINUS:
  return -prim( true );
 default:
  throw Error::syntax_error( "未知错误" );
 }
}
/*处理乘和除*/
double Parser::term( bool get )
{
 double left = prim( get );
 for( ; ; ) {
  switch( curr_tok ) {
  case MUL:
   left *= prim( true );
   break;
  case DIV:
   if( double d = prim( true ) ) {
    left /= d;
    break;
   }
   throw Error::zero_divide();
  default:
   return left;
  }
 }
}
/*处理加和减,程序里表现了对一行用';','/n'结尾的语法句的总处理*/
double Parser::expr( bool get )
{
 double left = term( get );
 for( ; ; ) {
  switch( curr_tok ) {
  case PLUS:
   left += term( true );
   break;
  case MINUS:
   left -= term( true );
   break;
  default:
   return left;
  }
 }
}
//table.cpp
/*******************************************************************
                  符号表
*********************************************************************/

/*关于map的定义,在大程序里,这样的全局变量少用*/
#include "dc.h"
std::map
      
      
        table; //main.cpp /********************************************************************************* 驱动函数 *********************************************************************************/ #include "dc.h" #include 
       
         //cin,cerr,cout的使用 using std::cin; using std::cerr; using std::cout; int main() { table[ "pi" ] = 3.1415926535897932385; //常用的符号值对可以添加 table[ "e" ] = 2.7182818284590452354; using namespace Lexer; //直接单纯的使用名字空间Lexer里的 东西 using namespace Error; //直接单纯的使用名字空间Error里的 东西 while( cin ) { try { get_token(); if( curr_tok == END ) break; if( curr_tok == PRINT ) continue; cout << Parser::expr( false ) << '/n'; //一个连环调用,显 示以';','/n'为结尾表达式的值 } catch( zero_divide ) { cerr << "0不可以作除数!/n"; //捕捉到除0的错误 if( curr_tok != PRINT ) Driver::skip(); } catch( syntax_error e ) { //捕捉到语法错误 cerr << "语法错误:" << e.p << '/n'; if( curr_tok != PRINT ) Driver::skip(); } } return Driver::no_of_errors; } int Driver::no_of_errors; //定义 /*错误处理,将这一行舍去*/ void Driver::skip() { no_of_errors++; while( cin ) { char ch; cin.get(ch); switch( ch ) { case '/n': case ';': return; } } } 
        
      
     
     
    
    
   
   
 
 


单头文件适合的是小程序的情况,当程序很大时,这样做很不好。
多头文件的情况是:
一个模块,可分为用户界面,实现界面,和实现。
例如对于parser,可分为:parser.h /*用户界面*/
parser_impl.h /*实现界面*/ 表示实现的共享的东西
parser.c  /*实现*/
多头文件提供了更好的局部性。

6.包含保护符
  #ifndef ABC_H
  #define ABC_H
   /*...*/
  #endif
  包含保护符使得重新编译的冗余减少了,因此被广泛的使用。

7.程序
  一组分别编译的单位由连接器组合起来,就是一个程序。程序的过程就是main()的执行过程。原则上在所有函数之外的变量,应该在main()调用之前完成初始化。在一个编译单位里的这种变量,按他们定义的顺序初始化。没有初始式的内部类型默认初始化为0。而在不同的编译单位里的全局变量,初始化顺序是没有保证的。全局变量的初始化抛出的异常无法捕捉。
全局变量有一种很好的替代物:通过函数返回的引用。
  int& use_count()
  {
        static int uc = 0;
        return uc;
  }
  void f()
  {
        cout << ++use_count();
        //...
  }
static变量只被初始化1次。
而有些这样的变量需要运行时初始化。那么以上说的都不是运行时的?
   程序的终止:
   1.exit()将调用静态对象的析构函数。abort()则不。
   2.调用exit()结束程序,调用它的函数及其调用者里的局部变量的析构函数都不会执行
1,2点看似矛盾。
   3.atexit()函数,程序在终止前可以执行一些代码。
提供给atexit()参数的函数不能有返回值也不能有参数,atexit()通过返回非0值表明达到最大限度?

/*红字表明疑问,不理解*/

 

 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值