对于既需要共享、又需要防止改变的数据应该声明为常类型(用 const 进行修饰)。
对于不改变对象状态的成员函数应该声明为常函数。
常类型
常对象
class A{
public:
A(int i, int j) {x=i; y=j;}
private:
int x, y;
};
A const a(3, 4); // a 是常对象,不能被更新
常成员
#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
*/
#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 类型说明符 &引用名;#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++ 程序的一般组织结构
一个工程可以划分为多个源文件:
利用工程来组合各个文件。
// 文件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;
}
外部变量
外部函数
将变量和函数限制在编译单元内
有的时候我们并不希望在当前文件中定义的这些标识符能拿到别的文件中去使用,这种情况下,我们可以把它限制在一个命名空间中来限制当前文件中定义的这些变量、函数都只能在当前文件中;
使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。
// 这里被 “namespace { …… }” 括起的区域都属于匿名的命名空间。
namespace { // 匿名的命名空间
int n;
void f() {
n++;
}
}
标准 C++ 库
编译预处理
条件编译指令
#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
第十九天学习精要 变量的作用域和生存期 标识符的作用域 变量名、函数名、类型名统称为“标识符”。一个标识符能够起作用的范围,叫做该标识符的作用域。 在一个标识符的作用域之外使用该标识符,会导致“标识符没有定义”的编译错误。 使用标识符的语句,必须出现在它们的声明或定义之后。 在单文件的程序中,结构、 函数和全局变量的作用域是其定义所在的整个文件。 函...
第十八天学习精要 结构 结构的概念 结构(struct) 用“struct”关键字来定义一个“结构”,也就定义了一个新的数据类型: 两个同类型的结构变量,可以互相赋值。但是结构变量之间不能用 “==”、“!=”、“<”、“>”、“&...
第十六天学习精要 指针和二维数组 指针和二维数组 如果定义二维数组: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指向的地方应该存放着一个类型为 ...
第十五天学习精要 指针作为函数参数 空指针 地址0不能访问。指向地址0的指针就是空指针。 可以用“NULL”关键字对任何类型的指针进行赋值。NULL实际上就是整数0,值为NULL的指针就是空指针。 指针可以作为条件表达式使用。如果指针的值为NULL,则相当于为假,值不为NULL,就相当于为真。 指针和数组 指针和数组 数组的名字是一个指针常量,指向数组的起始地址。 T a[...
第十天学习精要 数组越界 数组元素的下标,可以是任何整数,可以是负数,也可以大于数组的元素个数, 不会导致编译错误,但运行时很可能会出错,因为可能写入了别的变量的内存空间,或者写入指令的内存空间。 用变量作为数组下标时,不小心会导致数组越界(变量下标值变为负数,或者太大)。 可能引起意外修改其他变量的值,导致程序运行结果不正确。 可能试图访问不该访问的内存区域,导致程序崩溃。 数组越界的程序,用某...
第一天学习精要 信息在计算机中的表示 用0和1表示各种信息 计算机的电路由逻辑门电路组成。一个逻辑门电路可以看成一个开关,每个开关的状态是“开"(高电位)或“关”(低电位),即对应于1或0。 二进制数的一位,取值只能是0或1,称为一个“比特” (bit),简写:b。 八个二进制位称为一个“字节”(byte...
最初的步骤 使用带提示符的解释器 在 shell 提示符下,键入 Python 命令启动解释器。 对 Windows 用户,如果你已经配置好了 PATH 变量,那么就可在命令行中启动解释器。 如果使用 IDLE,点击 开始−>程序−>Python3.0−>IDLE(PythonGUI)开始 -> 程序 -&...
在没有排序的数中,找一个最小的数,并且和没有排序的数的第一个数交换 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]=...
函数声明——函数调用——函数定义 ...
多重继承 由于Lua语言中对象不是基本类型,因此在Lua语言中进行面向对象编程时有几种方式。在此,我们会看到允许在Lua语言中实现多重继承的一种实现。 这种实现的关键在于把一个函数用作 __index元方法。当一个表的元表中的__index字段为一个函数时,Lua不能在原来的表中找到一个键时就会调用这个函数。基于这点,我们就可以让__index元方法在其他期望的任意数量的父类中查找缺失的键。 多重...