c++学习日志(7.8-7.15)
来源:互联网 发布:php 过滤非utf8字符串 编辑:程序博客网 时间:2024/06/02 23:48
/*学习内容来自《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; } };}/*有关词法分析的名字空间*/#includenamespace 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
单头文件适合的是小程序的情况,当程序很大时,这样做很不好。
多头文件的情况是:
一个模块,可分为用户界面,实现界面,和实现。
例如对于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值表明达到最大限度?
/*红字表明疑问,不理解*/
- c++学习日志(7.8-7.15)
- C语言学习日志1!
- Objective-C学习日志1
- Objective-C学习日志3
- C语言学习日志 day1
- c语言学习日志 day3
- c语言学习日志 day4
- c语言学习日志 day5
- 日志系统开发学习总结(C#)
- Linux 学习日志:Linux, Linux C编程
- c语言操作mysql学习日志
- Objective-C语言学习日志(二)
- C语言程序设计学习日志(一)
- C语言入门——C语言学习日志1
- C语言入门——C语言学习日志2
- C语言入门——C语言学习日志3
- C语言入门——C语言学习日志4
- C语言入门——C语言学习日志5
- 过敏性鼻炎的预防
- 未婚男子健康生活100条感悟
- 开博说明
- LUA语言学习教程 [转]
- Lua 语言的简单介绍 [转]
- c++学习日志(7.8-7.15)
- const用法小结
- LUA教程
- BSD许可证的条款
- VS2005中Project不能Debug
- SSH三框架结合的配置
- 牛肉面的故事
- 一点感受
- 上海之行第一天