C++——程序的结构
1、作用域和可见性
1.1 函数原型中的参数其作用域仅在()内。因此参数名称可有可无,但是参数类型需要声明。
1.2 块作用域 在块中声明的标识符其作用域自声明处起,限于块中。
1.3 类作用域 类作用域作用于特定的成员名。如类X的成员M具有类作用域,对M的访问方式如下:
如果在X的成员函数中没有声明同名的局部作用标识符,那么该函数内可以访问成员M;
通过表达式X.M或者X::M以及prt->M访问。
1.4 在所有类和函数之外出现的声明,具有文件作用域,开始于声明处,结束于文件结尾。
2、可见性 可见性是从对标识符的引用的角度来说的,表示从内层作用域往外层作用域看的时候能看见什么
作用域: 文件作用域>类作用域>块作用域
2.1 标识符声明在前引用在后,如果某个标识符声明在外层,且在内层没有与之同名的标识符,则该标识符在内层可见;
2.2 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见,也就是在内层被屏蔽。
#include <iostream> using namespace std; int i; //全局变量,文件作用域 int main() { i=5; //文件作用域的i赋初值 { //子块1 int i; //局部变量,块作用域 i=7; cout<<"i="<<i<<endl; //输出7 } cout<<"i="<<i<<endl; //输出5 }
3、对象的生存期 对象从产生到结束的这段时间。在对象生存期内,对象将保持其值,直到被更新。
3.1 静态生存期与程序的运行期相同,在文件作用域中声明的对象具有这种生存期,在函数内部声明静态生存期对象要冠以关键字static;
#include <iostream> using namespace std; int i; //全局变量,文件作用域 int main() { i=5; //文件作用域的i赋初值,具有静态生存期 cout<<"i="<<i<<endl; //输出5 return 0; }
3.2 动态生存期块作用域中声明的,没有static修饰的对象,通常也称为局部生存期对象,开始于声明点,结束于该标识符的作用域结束处。
#include<iostream.h> void fun(); int main() { fun(); fun(); } void fun()//静态生存期对象a.第一次被调用a=1,i=5,运算之后a=2,i=6,第二次被调用时,由于a具有静态生存期,a的值保持不变,为2,具有动态生存期的i已经被释放,运算之后a=3,i=6。 { static int a=1; int i=5; a++; i++; cout<<"i="<<i<<",a="<<a<<endl; }
#include<iostream.h> int i=1; // i 为全局变量,具有静态生存期。 int main() { static int a;// 静态局部变量,有全局寿命,局部可见。 int b=-10; // b, c为局部变量,具有动态生存期。 int c=0; void other(void); cout<<"---MAIN---\n"; cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; c=c+8; other(); cout<<"---MAIN---\n"; cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; i=i+10; other(); } void other(void) { static int a=2; static int b; // a,b为静态局部变量,具有全局寿命,局部可见。 //只第一次进入函数时被初始化。 int c=10; // C为局部变量,具有动态生存期, //每次进入函数时都初始化。 a=a+2; i=i+32; c=c+5; cout<<"---OTHER---\n"; cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; b=a; }
运行结果:
---MAIN---
i: 1 a: 0 b: -10 c: 0
---OTHER---
i: 33 a: 4 b: 0 c: 15
---MAIN---
i: 33 a: 0 b: -10 c: 8
---OTHER---
i: 75 a: 6 b: 4 c: 15
时钟程序:
#include<iostream> using namespace std; class Clock //时钟类定义 { public: //外部接口 Clock(); void SetTime(int NewH, int NewM, int NewS); //三个形参均具有函数原型作用域 void ShowTime(); ~Clock(){} private: //私有数据成员 int Hour,Minute,Second; }; //时钟类成员函数实现 Clock::Clock() //构造函数 { Hour=0; Minute=0; Second=0; } void Clock::SetTime(int NewH, int NewM, int NewS) { Hour=NewH; Minute=NewM; Second=NewS; } void Clock::ShowTime() { cout<<Hour<<":"<<Minute<<":"<<Second<<endl; } Clock globClock; //声明对象globClock,具有静态生存期,文件作用域 // 由缺省构造函数初始化为0:0:0 int main() //主函数 { cout<<"First time output:"<<endl; //引用具有文件作用域的对象globClock: globClock.ShowTime(); //对象的成员函数具有类作用域 //显示0:0:0 globClock.SetTime(8,30,30); //将时间设置为8:30:30 Clock myClock(globClock); //声明具有块作用域的对象myClock //调用拷贝构造函数,以globClock为初始值 cout<<"Second time output:"<<endl; myClock.ShowTime(); //引用具有块作用域的对象myClock //输出8:30:30 }
4、数据与函数
可以通过函数间的参数传递实现数据共享,更方便的将数据存储在全局对象中,省去了传递的空间,如果将数据和使用数据的函数封装在类中是面向对象的方法。
#include<iostream.h>//使用全局对象实现数据共享,但是使用全局变量会使得程序结构更复杂,而且牵一发而动全身 int global; void f() { global=5;} void g() { cout<<global<<endl;} int main() { f(); g(); //输出“5” return 0; } #include<iostream.h>//将数据和函数封装在类中实现数据共享 class Application { public: void f();void g(); private: int global; }; void Application::f() { global=5;} void Application::g() {cout<<global<<endl;} int main() { Application MyApp;//建立一个新对象来访问函数,该对象似乎不需要改对象,该处有点多余,有待解决(静态成员) MyApp.f(); MyApp.g(); return 0; }
5、静态成员
静态数据成员 用static声明,属于整个类的成员,并不需要通过对象去访问。该类的所有对象维护该成员的同一个拷贝,必须在类外定义和初始化,用::来指明所属的类。
静态成员函数 类外代码可以使用类名和作用域操作符来调用静态数据成员,静态数据成员函数只能引用属于该类的静态数据成员或者静态数据函数。
#include<iostream.h> class Application { public: static void f(); static void g(); private: static int global; }; int Application::global=0;//在类外初始化 void Application::f() { global=5;} void Application::g() {cout<<global<<endl;} int main() { Application::f();//不建立对象,直接通过类名来访问。 Application::g(); return 0; }
注意事项:静态成员数据和函数属于整个类,不属于任何对象,但是可以通过对象来调用,也可以通过类名来调用。
class A { public: static void f(A a); private: int x; }; void A::f(A a) { cout<<x; //对x的引用是错误的 cout<<a.x; //正确 } #include <iostream.h> class Point //Point类声明 {public: //外部接口 Point(int xx=0,int yy=0){X=xx;Y=yy;countP++;} Point(Point &p);//拷贝构造函数 int GetX() {return X;} int GetY() {return Y;} static void GetC() {cout<<" Object id="<<countP<<endl;} private: //私有数据成员 int X,Y; static int countP; } Point::Point(Point &p) { X=p.X; Y=p.Y; countP++; } int Point::countP=0; int main() //主函数实现 { Point A(4,5); //声明对象A cout<<"Point A,"<<A.GetX()<<","<<A.GetY(); A.GetC(); //输出对象号,对象名引用 Point B(A); //声明对象B cout<<"Point B,"<<B.GetX()<<","<<B.GetY(); Point::GetC(); //输出对象号,类名引用 }
6、友元
友元是C++提供的一种破坏数据封装和数据隐藏的机制,包括友元函数和友元类,是通过将一个模块声明为另一个模块的友元,一个模块就能够引用到另个模块中本是被隐藏的信息,为了确保数据的完整性和数据封装与隐藏的原则,建议尽量不使用或少使用友元。
友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体能够通过对象名访问private和protected成员,增加了灵活性,使得程序员可以在封装和快速性方面做合理选择,访问对象中的成员必须通过对象名。
#include <iostream.h>//计算两点距离 #include <math.h> class Point //Point类声明 { public: //外部接口 Point(int xx=0, int yy=0) {X=xx;Y=yy;} int GetX() {return X;} int GetY() {return Y;} friend float Distance(Point &a, Point &b); //友元函数 private: //私有数据成员 int X,Y; }; double Distance( Point& a, Point& b) { double dx=a.X-b.X;//在类外通过对象直接访问类point的私有数据成员 double dy=a.Y-b.Y; return sqrt(dx*dx+dy*dy); } int main() { Point p1(3.0, 5.0), p2(4.0, 6.0); double d=Distance(p1, p2); cout<<"The distance is "<<d<<endl; return 0; }
若一个类为另一个类的友元,则此类的所有成员都可以访问对方类的私有成员,声明语法:将友元类名在另个类使用friend修饰。
class A { friend class B;//友元类 public: void Display() {cout<<x<<endl;} private: int x; } class B { public: void Set(int i); void Display(); private: A a; }; void B::Set(int i)//可以访问并修改A类的私有成员 { a.x=i; } void B::Display() { a.Display(); }
注意事项:友元关系式单向的,如果声明A是B的友元,B的成员函数可以访问A的私有和保护数据,但是反之不然。
7、共享数据的保护
常类型:常类型的对象必须初始化,而且不能被更新,实现对共享数据的保护。
常引用:被引用的对象不能被更新。 const 类型说明符 &引用名
常对象:必须进行初始化,不能被更新。类名 const 对象名
常数组:数组元素不能被更新。 类型说明符 const 数组名[大小]
常指针:指向常量的指针
用const修饰的对象成员
常成员函数:使用const说明,常成员函数不更新对象的数据成员,const是函数类型的一个组成部分,在实现函数的部分也要带const关键字,const也可以用于区分重载函数。通过对象只能调用它的常成员函数,不能通过常对象去调用普通数据函数。
#include<iostream> using namespace std; class R { public: R(int r1, int r2){R1=r1;R2=r2;} void print(); void print() const;//print函数的重载 private: int R1,R2; }; void R::print() { cout<<R1<<":"<<R2<<endl; } void R::print() const { cout<<R1<<";"<<R2<<endl; } int main() { R a(5,4); a.print(); //调用void print() const R b(20,52); //定义常对象来调用常成员函数print() b.print(); //调用void print() const } #include<iostream> using namespace std; class A { public: A(int i); void print(); const int& r;//常引用 private: const int a; static const int b; //静态常数据成员 }; const int A::b=10; //静态常数据成员在类外说明和初始化 A::A(int i):a(i),r(a) //常数据成员只能通过初始化列表来获得初值 { } void A::print() { cout<<a<<":"<<b<<":"<<r<<endl; } int main() { A a1(100),a2(0); a1.print(); a2.print(); }
8、编译预处理命令
#include 包含指令 将一个源文件嵌入到当前源文件中该点处。
#include<文件名> 按标准方式搜索,文件位于C++系统目录的include子目录下
#include"文件名" 首先在当前目录中搜索,若没有,再按标准方式搜索。
#define 宏定义指令 定义符号常量,很多情况下已被const定义语句取代。定义带参数宏,已被内联函数取代
#undef 删除由#define定义的宏,使之不再起作用。
条件编译指令 #if 和 #endif
#if 常量表达式
//当“ 常量表达式”非零时编译
程序正文
#endif
......
条件编译指令——#else
#if 常量表达式
//当“ 常量表达式”非零时编译
程序正文1
#else
//当“ 常量表达式”为零时编译
程序正文2
#endif
条件编译指令 #elif
#if 常量表达式1
程序正文1 //当“ 常量表达式1”非零时编译
#elif 常量表达式2
程序正文2 //当“ 常量表达式2”非零时编译
#else
程序正文3 //其他情况下编译
#endif
条件编译指令
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2。
条件编译指令
#ifndef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。
多文件结构
一个源程序可以划分为多个源文件:
类声明文件(.h文件)
类实现文件(.cpp文件)
类的使用文件(main()所在的.cpp文件)
利用工程来组合各个文件。
不使用条件编译的头文件
//main.cpp #include "file1.h" #include "file2.h" int main() { … } //file1.h #include "head.h" … //file2.h #include "head.h" … //head.h … class Point { … } …
使用条件编译的头文件
//head.h #ifndef HEAD_H #define HEAD_H … class Point { … } … #endif