代码先锋网 代码片段及技术文章聚合

编程菜鸟到大佬之路:C++程序设计(十七)

第五章 数据的共享与保护

共享数据的保护


  • 对于既需要共享、又需要防止改变的数据应该声明为常类型(用 const 进行修饰)。

  • 对于不改变对象状态的成员函数应该声明为常函数。

  • 常类型

    • 常对象

      • 必须在定义的时候进行初始化,一旦初始化以后就不能再被更新;
      • 在定义对象的时候前面加一个 const 来修饰;
      • 用 const 修饰的对象(const 类名 对象名):
      class A{
      public:
      	A(int i, int j) {x=i; y=j;}
      private:
      	int x, y;
      };
      A const a(3, 4); 	// a 是常对象,不能被更新
      
    • 常成员

      • 用 const 进行修饰的类成员:常数据成员和常函数成员;
      • 用 const 修饰的对象成员;
      • 常成员函数
        • 常成员函数就是专门用来处理常对象的,它是用const来修饰的;
        • 这样的函数相当于它做出了承诺绝不改变对象的状态,所以在这样的函数原型的最后要加 const;
        • 常成员函数不更新对象的数据成员;
        • 常成员函数说明格式:类型说明符 函数名(参数表) const;(这里 const 是函数类型的一个组成部分,因此在实现部分也要带 const 关键字);
        • const 关键字可以被用于参与对重载函数的区分;
        • 一个普通对象调用常函数是没有任何问题的。
      #include "pch.h"
      #include <string>
      #include <iostream>
      using namespace std;
      
      /*
      这个 R 类里面定义了两个 print 函数,其中一个后面有 const,是个常函数。
      这两个 print 函数的参数表都是空的,但是其中一个后缀了 const,
      也是合法的重载形式,const 也是区分重载函数的一个因素。
      */
      class R {
      public:
      	R(int r1, int r2) : r1(r1), r2(r2) { }
      	void print();
      	void print() const;
      private:
      	int r1, r2;
      };
      
      void R::print() {
      	cout << r1 << ";" << r2 << endl;
      }
      
      // 这个有 const 后缀的这个函数特别地承诺了这个函数绝不改变对象的状态
      /*
      必须要显式地标出来 const,编译器在编译的时候才会去审查函数体有没有改变状态的语句。
      如果在其中有改变对象状态的语句,它就会报语法错。
      但是如果你不后缀 const,编译器在编译的时候就不会去审查这一点,
      所以就不能保证这个函数不改变对象状态。
      */
      void R::print() const {
      	cout << r1 << ";" << r2 << endl;
      }
      
      int main() {
      	R a(5, 4);
      	a.print();		// 调用 void print()
      	// 一个常对象只能用常函数去处理它,不是常函数不能通过常对象调用
      	const R b(20, 52);
      	b.print();  // 调用 void print() const
      	return 0;
      }
      /*
      输出结果:
      5;4
      20;52
      */
      
      • 通过常对象只能调用它的常成员函数;
      • 常数据成员
        • 就是用 const 修饰的数据成员。
      #include "pch.h"
      #include <string>
      #include <iostream>
      using namespace std;
      
      class A {
      public:
      	A(int i);
      	void print();
      private:
      	const int a;
      	static const int b;   // 静态常数据成员
      };
      
      const int A::b = 10;	// 静态常数据成员在类外说明和初始化
      
      // 常数据成员只能通过初始化列表来获得初值
      A::A(int i) : a(i) { }
      
      void A::print() {
      	cout << a << ":" << b << endl;
      }
      int main() {
      	/* 建立对象 a 和 b,并以100和0作为初值,分别调用构造函数,通过构造函数的初
      		始化列表给对象的常数据成员赋初值 */
      	A a1(100), a2(0);
      	a1.print();
      	a2.print();
      	return 0;
      }
      /*
      输出结果:
      100:10
      0:10
      */
      
    • 常引用

      • 引用是可以达到数据双向传递的,如果我们既要用引用又不想被引用的对象被更新,不想达到双向传递,可以将参数定义为常引用也就是只读的引用;
      • 如果在声明引用时用 const 修饰,被声明的引用就是常引用;
      • 常引用所引用的对象不能被更新;
      • 如果用常引用做形参,便不会意外地发生对实参的更改;
      • 常引用的声明形式如下:
        const 类型说明符 &引用名;
      #include "pch.h"
      #include <string>
      #include <iostream>
      using namespace std;
      
      class Point {	// Point 类定义
      public:		// 外部接口
      	Point(int x = 0, int y = 0) : x(x), y(y) { }
      	int getX() { return x; }
      	int getY() { return y; }
      	// 在这两个参数前面都加了 const 说明这两个参数都是常引用
      	/* 这样我们就可以比较放心地用这个友元函数了,
      	既提高了访问效率让它能够直接访问私有成员,
      	又不会因此对私有成员的安全性有任何破坏 */
      	friend float dist(const Point &p1, const Point &p2);
      private:	// 私有数据成员
      	int x, y;
      };
      
      float dist(const Point &p1, const Point &p2) {	// 常引用作形参
      	double x = p1.x - p2.x;
      	double y = p1.y - p2.y;
      	return static_cast<float>(sqrt(x * x + y * y));
      }
      
      int main() {	//主函数
      	const Point myp1(1, 1), myp2(4, 5);	//定义Point类的对象
      	cout << "The distance is: ";
      	cout << dist(myp1, myp2) << endl;	//计算两点间的距离
      	return 0;
      }
      /*
      输出结果:
      The distance is: 5
      */
      
    • 常数组

      • 数组元素不能被更新。
        类型说明符 const 数组名[大小]
        
    • 常指针

      • 指向常量的指针。

多文件结构和编译预处理命令


  • 我们在写程序的时候,如果需要分工合作,如果程序的规模略微大一些,我们就会需要把程序分别放在不同的文件中,可以分别编译然后放在一起连接。

  • C++ 程序的一般组织结构

    • 一个工程可以划分为多个源文件:

      • 可以将类的声明放在头文件中就是后缀 .h 的文件(类声明文件——.h文件);
      • 可以把类成员函数的实现放在一个或多个独立的 C++ 文件中(类实现文件——.cpp文件);
      • 使用类的人和写这个类的代码的人可能不是一个人,也可能就不是一个团队的人,那么这种情况下,使用类的文件和类的定义以及实现文件往往是分开的,可以模拟这种情况将主函数所在的文件放在另一个独立的 C++ 文件中(类的使用文件——main()所在的.cpp文件)。
    • 利用工程来组合各个文件。

      // 文件1,类的定义,Point.h
      /* 系统类库里面提供的这些头文件都是不带后缀 h 的,
      而我们自己写的头文件通常是带后缀 h 的 */
      
      class Point {	// 类的定义
      public:		// 外部接口
      	Point(int x = 0, int y = 0) : x(x), y(y) { }
      	Point(const Point &p);
      	~Point() { count--; }
      	int getX() const { return x; }
      	int getY() const { return y; }
      	static void showCount();	// 静态函数成员
      private:	// 私有数据成员
      	int x, y;
      	static int count;	// 静态数据成员
      };
      
      // 文件2,类的实现,Point.cpp
      /* 我们自己写的这些头文件就要用双引号引起来,
      而系统类库里面的头文件用尖括号括起来。
      用尖括号括起来的这样的头文件,
      它会直接到你安装的时候的默认目录下去找这个文件。
      而用双引号引起来的会首先在当前的工作目录下去找这个文件,
      如果找不到才会到系统约定的目录下去找。
      所以我们自己定义的头文件一般是用双引号引起来而且后缀是有 h 的。*/
      
      #include "Point.h"
      #include <iostream>
      using namespace std;
      
      int Point::count = 0;	// 使用类名初始化静态数据成员
      
      Point::Point(const Point &p) : x(p.x), y(p.y) {		// 拷贝构造函数体
      	count++;
      }
      
      void Point::showCount() {
      	cout << "  Object count = " << count << endl;
      }
      
      // 文件3,主函数,5_10.cpp
      #include "Point.h"
      #include "pch.h"
      #include <string>
      #include <iostream>
      using namespace std;
      
      int main() {
      	Point a(4, 5);	// 定义对象 a,其构造函数使 count 增1
      	cout << "Point A: " << a.getX() << ", " << a.getY();
      	Point::showCount();		// 输出对象个数
      
      	Point b(a);		// 定义对象 b,其构造函数使 count 增1
      	cout << "Point B: " << b.getX() << ", " << b.getY();
      	Point::showCount();		// 输出对象个数
      
      	return 0;
      }
      
  • 外部变量

    • 如果一个变量除了在定义它的源文件中可以使用外,还能被其它文件使用,那么就称这个变量是外部变量;
    • 文件作用域中定义的变量,默认情况下都是外部变量,但在其它文件中如果需要使用这一变量,需要用 extern 关键字加以声明。
  • 外部函数

    • 函数定义的位置和调用的位置很可能不在同一个文件中,如果我们想使用在别的文件中定义的函数,这种情况下叫做使用外部函数;
    • 在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的;
    • 如果我们要使用一个在别处定义的函数,只要在当前文件中在使用之前声明函数的原型就可以了;
    • 这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可,也可以在声明函数原型或定义函数时用 extern 修饰,其效果与不加修饰的默认状态是一样的。
  • 将变量和函数限制在编译单元内

    • 有的时候我们并不希望在当前文件中定义的这些标识符能拿到别的文件中去使用,这种情况下,我们可以把它限制在一个命名空间中来限制当前文件中定义的这些变量、函数都只能在当前文件中;

    • 使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。

      // 这里被 “namespace { …… }”  括起的区域都属于匿名的命名空间。
      namespace { 	// 匿名的命名空间
      	int n;
      	void f() {
      		n++;
      	}
      }
      
  • 标准 C++ 库

    • 标准 C++ 类库是一个极为灵活并可扩展的可重用软件模块的集合;
    • 标准 C++ 类与组件在逻辑上分为六种类型:
      • 输入/输出类(cin 对象、cout 对象就是输入输出类的预定义好的对象)
      • 容器类与抽象数据类型(大批量数据在程序中存储)
      • 存储管理类
      • 算法
      • 错误处理
      • 运行环境支持
  • 编译预处理

    • #include 包含指令
      • 它能将一个头文件包含进来,也就是把它的内容插入到当前的位置,即将一个源文件嵌入到当前源文件中该点处;
      • 包含头文件有两种方式:
        • #include<文件名>(用尖括号括起文件名)
          • 按标准方式搜索,文件位于 C++ 系统目录的 include 子目录下。
        • #include"文件名"(用双引号括起文件名)
          • 首先在当前目录中搜索,若没有,再按标准方式搜索。
    • #define 宏定义指令
      • 定义符号常量,很多情况下已被 const 定义语句取代;
      • 定义带参数宏,已被内联函数取代;
      • 在 C++ 中仍然有的时候会用 define 这个宏定义指令,那只是为了定义一些符号、标记用于在其他的编译预处理指令中,比如说进行选择性编译。
    • #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

      // 如果“标识符”经 #defined 定义过,且未经 undef 删除,则编译程序段1;否则编译程序段2。
      #ifdef 标识符
      	程序段1
      #else
      	程序段2
      #endif
      
    • #ifndef

      // 如果“标识符”未被定义过,则编译程序段1;否则编译程序段2。
      #ifndef 标识符
      	程序段1
      #else
      	程序段2
      #endif
      
版权声明:本文为faker1895原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/faker1895/article/details/88669870

智能推荐

编程菜鸟到大佬之路:C语言程序(十九)

第十九天学习精要 变量的作用域和生存期 标识符的作用域 变量名、函数名、类型名统称为“标识符”。一个标识符能够起作用的范围,叫做该标识符的作用域。 在一个标识符的作用域之外使用该标识符,会导致“标识符没有定义”的编译错误。 使用标识符的语句,必须出现在它们的声明或定义之后。 在单文件的程序中,结构、 函数和全局变量的作用域是其定义所在的整个文件。 函...

编程菜鸟到大佬之路:C语言程序(十八)

第十八天学习精要 结构 结构的概念 结构(struct) 用“struct”关键字来定义一个“结构”,也就定义了一个新的数据类型: 两个同类型的结构变量,可以互相赋值。但是结构变量之间不能用 “==”、“!=”、“<”、“>”、“&...

编程菜鸟到大佬之路:C语言程序(十六)

第十六天学习精要 指针和二维数组 指针和二维数组 如果定义二维数组:T a[M][N]; a[i] (i是整数)是一个一维数组 a[i]的类型是 T * sizeof(a[i]) = sizeof(T) * N a[i]指向的地址: 数组a的起始地址 + i×N×sizeof(T) 指向指针的指针 定义:T ** p; p是指向指针的指针,p指向的地方应该存放着一个类型为 ...

编程菜鸟到大佬之路:C语言程序(十五)

第十五天学习精要 指针作为函数参数 空指针 地址0不能访问。指向地址0的指针就是空指针。 可以用“NULL”关键字对任何类型的指针进行赋值。NULL实际上就是整数0,值为NULL的指针就是空指针。 指针可以作为条件表达式使用。如果指针的值为NULL,则相当于为假,值不为NULL,就相当于为真。 指针和数组 指针和数组 数组的名字是一个指针常量,指向数组的起始地址。 T a[...

编程菜鸟到大佬之路:C语言程序(十)

第十天学习精要 数组越界 数组元素的下标,可以是任何整数,可以是负数,也可以大于数组的元素个数, 不会导致编译错误,但运行时很可能会出错,因为可能写入了别的变量的内存空间,或者写入指令的内存空间。 用变量作为数组下标时,不小心会导致数组越界(变量下标值变为负数,或者太大)。 可能引起意外修改其他变量的值,导致程序运行结果不正确。 可能试图访问不该访问的内存区域,导致程序崩溃。 数组越界的程序,用某...

猜你喜欢

编程菜鸟到大佬之路:C语言程序(一)

第一天学习精要 信息在计算机中的表示 用0和1表示各种信息 计算机的电路由逻辑门电路组成。一个逻辑门电路可以看成一个开关,每个开关的状态是“开"(高电位)或“关”(低电位),即对应于1或0。 二进制数的一位,取值只能是0或1,称为一个“比特” (bit),简写:b。 八个二进制位称为一个“字节”(byte...

编程菜鸟到大佬之路:简明python教程(二)

最初的步骤 使用带提示符的解释器 在 shell 提示符下,键入 Python 命令启动解释器。 对 Windows 用户,如果你已经配置好了 PATH 变量,那么就可在命令行中启动解释器。 如果使用 IDLE,点击 开始−&gt;程序−&gt;Python3.0−&gt;IDLE(PythonGUI)开始 -&gt; 程序 -&...

C语言程序设计(十七)12.12

在没有排序的数中,找一个最小的数,并且和没有排序的数的第一个数交换 min=a[0] k=0 for(j=0;j<10;i++) { if(min>a[j]) { k=j min=a[j]; } } min a[k] k=0 for(j=0;j<10;i++) { if(a[k]>a[j]) { k=j } } //a[k] t=a[0]; a[0]=a[k]; a[k]=...

C语言学习系列十七——函数程序设计

函数声明——函数调用——函数定义   ...

Lua程序设计(四十七)

多重继承 由于Lua语言中对象不是基本类型,因此在Lua语言中进行面向对象编程时有几种方式。在此,我们会看到允许在Lua语言中实现多重继承的一种实现。 这种实现的关键在于把一个函数用作 __index元方法。当一个表的元表中的__index字段为一个函数时,Lua不能在原来的表中找到一个键时就会调用这个函数。基于这点,我们就可以让__index元方法在其他期望的任意数量的父类中查找缺失的键。 多重...