文章目录
C++笔记3
数据的共享与保护
标识符的作用域与可见性
作用域
作用域共有五种,从小到大一次为:函数原型作用域–>局部作用域–>类作用域–>文件作用域–>命名空间作用域。
-
函数原型作用域
例如:函数声明
double area(double radius);
里的变量radius,只在area函数的声明的参数列表内起作用,即从括号左边到括号右边。 -
局部作用域(块作用域)
大括号括起来的部分内部定义的变量,它的作用域在开始定义处到大括号结束。
例如函数的形参和if语句、switch语句、for语句、while语句内定义的变量,都遵守上述规则。
具有局部作用域的变量称为局部变量。
-
类作用域
类的成员具有类作用域。
在类内和成员函数内可以直接访问。在类外访问,可以通过
x.m
(非静态成员)和X::m
(静态成员)等方式访问;还可以通过对象指针和对象引用访问。 -
文件作用域(又被称为静态作用域)
不在前述各个作用域中声明的标识符,就具有文件作用域。文件作用域的标识符起始于声明处,结束于文件结尾。
-
命名空间作用域
命名空间作用域的解释详见C++程序设计(郑莉)第四版P147。
可见性
可见性是作用域最小的标识符最大
对象的生存期
这里的对象指的不是狭义的对象,包括了程序中的简单变量和对象,函数等等
静态生存期
- 这种生存期与程序的运行期相同–>只要在程序运行期间,都是生存的。
- 在文件作用域中声明的对象具有这种生存期
- 在函数内部要声明静态生存期对象,要冠以static关键词
动态生存期
- 开始于程序执行到声明点时,结束于命名该标识符的作用域结束处
- 块作用域中声明的,没有冠以static关键词的对象,是动态生存期对象
类的静态成员
静态数据成员
在类内声明,在类外定义和初始化。
静态函数成员
使用规则与静态数据成员相同
通常用来对静态数据成员进行操作
class J{
public:
...
static void showCount(); //静态函数成员的声明
private:
static int count; //静态数据成员的声明
};
int J::count =90; //静态数据成员的定义和初始化
//注意,定义时不能带有static关键词修饰
void J::showCount() { //在类体外进行静态函数成员的实现
cout << "count:" << count << endl;
}
int main(){
...
J::showCount();
return 0;
}
/*注意,此程序段只显示了与静态成员相关的代码,其它代码已经省略
在使用时根据需要在添加相应的代码
*/
友元
类的友元函数可以让程序在类外直接通过对象名来访问对象的数据成员(友元函数不是类的成员函数),从而绕过类的外部接口,提高了程序的效率。有时候,程序员必须在效率和数据安全二选一,通过友元函数可以进行二者的合理选择。
–>tips:当使用对象作为函数参数时,直接传递对象的引用比传递整个对象的数据的效率更高。但是直接传递引用的方式会有隐患。
友元函数
友元函数的使用:
class Point {
public:
Point(int x=0,int y=0):x(x),y(y){}//构造函数
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &p1, Point& p2); //友元函数的声明,使用了friend关键字
private:
int x, y;
};
float dist(Point &p1, Point& p2) { //在类外实现了友元函数,没有friend修饰
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return static_cast<float>(sqrt(x*x + y*y)); //static_cast强制类型转换
}
int main()
{
Point p1(1, 1);
Point p2(2, 2);
cout << dist(p1, p2) << endl;
return 0;
}
- 使用友元函数可以在类外通过对象名访问对象的private和protected成员
友元类
若一个类为另一个类的友元,那这个类的所有函数成员可以访问另一个类的所有私有成员。
若 A 类为 B 类的友元类,则 A 类的所有成员函数都是 B 类的友元函数,都可以访问 B 类的私有和保护成员。
P161
声明语法:使用friend关键字将友元类的类名在类中声明。
友元的关系是单向的,如果A是B的友元类,那么B不一定是A的友元类。
共享数据的保护
数据的保护主要就是通过const关键字实现的。
注意:const 关键字和static不一样,const在成员(数据成员和函数成员)被声明时要使用,在数据成员初始化处和函数成员的实现处也必须带上。简言之,被const修饰的标识符,无论何时出现,都要带着const。
常类型:
常对象
声明方式:const 类型 对象名;
常对象在其整个生存周期内它的数据成员不能被更新。
常对象必须进行初始化,而且不能被更新。
常函数
-
常函数可以被常对象调用,也可以被普通对象调用。
-
常对象只能调用常成员函数,不能调用其它成员函数
-
常成员函数不能更新目的对象的数据成员–》因为在常成员函数被调用时,其目的对象被视为常对象。
-
const关键字可以实现对函数重载的区分
对于无须改变对象状态的成员函数,都应当使用 const 修饰
class R {
public:
R(int r1,int r2):r1(r1),r2(r2){} //构造函数
void print() { //普通成员函数
cout << r1 << ":" << r2 << endl; //这个普通的print()函数也可以没有。若删去则r调用const修饰的print函数
}
void print() const { //常成员函数
cout << r1 << "---:---" << r2 << endl;
}
private:
int r1, r2;
};
int main()
{
R r(2, 3);
r.print(); //对象r调用的是普通的成员函数print()
const R rcon(4, 4);
rcon.print(); //对象rcon调用的是常函数print()
return 0;
}
/*输出结果:
2:3
4---:---4
*/
常数据成员
static 数据成员和非静态成员都可以用 const 修饰。
常成员只能初始化赋值,不能在其它地方赋值。
–》const成员分两类:静态的和非静态的
静态成员(static):在类外直接初始化。初始化也不可以在main函数内部进行
非静态成员:通过类的构造函数的初始化列表进行初始化。注意:不能将const修饰的非静态成员放在构造函数函数体内进行赋值。
class R {
public:
R(int i);
void print() {
cout << r1 << ":" << r2 << endl;
}
private:
static const int r1;
const int r2;
};
R::R(int i) :r2(i) {} //非静态数据成员被const修饰,只能在构造函数的初始化列表里进行初始化操作
const int R::r1 = 5; //常静态成员(static const)只能在类外且非main函数内部进行初始化
int main(){
R a(66);
a.print();
return 0;
}
常引用
常引用所引用的对象不能被更新
如果用常引用作形参,便不会意外地发生对实参的更改
多文件结构
先附上一篇关于C/C++工程多文件结构的讲解:一篇关于C/C++工程多文件结构的讲解CSDN
例如Point类的使用这一程序,我们可以将程序改为多文件结构。
- 文件 Point.h 里写类Point的声明;
- 文件 Point_shixian.cpp 需要包含Point.h 头文件里写类Point的各个成员函数的实现以及常数据成员的处理等。
- main.cpp 需要包含Point.h 头文件。实现对Point类的使用。
每个CPP文件都是各自编译成后缀为.obj 的文件的,然后把所有的.obj文件和系统运行库连接起来,成为一个可执行文件(xxx.exe)
外部变量
外部变量:除了在定义它的源文件中可以使用外,还可以在其它的源文件中使用。
- 文件作用域中定义的变量,默认都是外部变量(int a;)
- 在使用其他源文件中定义的变量时,要在本文件内声明(extern int a;)然后方可使用。
外部函数
外部函数:在类外声明的函数(即非成员函数)都是具有文件作用域的可作为外部函数
- 外部函数都可以在其它的编译单元中别调用
- 在调用其它源文件中定义的函数之前,要先在本文件中声明函数原型。
通常情况下,变量和函数的定义都放在源文件中,而对外部变量和外部函数的引用性声明则放在头文件中。
如何将本文件中定义的变量和函数隐藏而不被其它源文件使用呢?
可以在匿名命名空间内定义这样的变量和函数。
namespace {//匿名命名空间
int b_yincang;
void print_b() {
cout << "b = " << b_yincang << endl;
}
}
编译预处理指令
#include指令
#include 指令可以嵌套使用。假设有一个头文件 myhead. h ,该头文件中又可以有
如下的文件包含指令:
#include “file1.h”
#include “file2.h”
P173
- #include<>,一般用来包含系统的头文件,这样使用时,系统会按标准方式搜索被包含文件,即先从系统默认的include文件夹内搜索
- #include"" , 一般用来包含自定义的头文件,这样使用时,编译器会首先在当前目录中搜索,若没有,再按标准方式搜索。
#define 指令和#undef指令
#define指令经常在C语言中使用,但是在C++中有了更好的方式足以替代它,所有#define指令经常用来定义空符号如
#define MYHEAD_H
,定义它的目的,仅仅是表示 "MYHEAD_H 已经定义过"这样一种状态。配合条件编译指令一起使用,这是C++中常用的方式。
#undef
指令的作用是删除#define
定义过的符号,使其不再产生作用
条件编译指令
多种形式使用
-
#if 常量表达式1 //程序段1 #elif 常量表达式2 //程序段2 #elif 常量表达式2 //程序段3 #else //程序段4 #endif
-
#ifdef 标识符 //如果标识符被#define定义过且没有被#undef删除,则编译程序段1 程序段1 #else //else可以删去若不需要 程序段2 #endif
-
#ifndef 标识符 //如果标识符1没有定义过,则编译程序段1,否则编译程序段2 程序段1 #else //else是可以删去的如果不需要的话 程序段2 #endif
-
defined操作符
defined 是一个预处理操作符,所以不需要带#号,使用方法为:
defined(标识符)
,若该标识符被定义过,则上述表达式为非零,否则为零。下面两种写法完全等效:
#ifndef MYHEAD_H # define MYHEAD_H 程序段1 #endif //等价于 #if !defined(MYHEAD_H) #define MYHEAD_H 程序段1 # endif
使用条件编译指令的作用之一是,防止某个头文件被多次包含导致的类和变量的重复定义错误。(出现这种错误的原因是,#include指令是可以嵌套的,即头文件可以嵌套包含)
详见P176
不需要带#号,使用方法为:defined(标识符)
,若该标识符被定义过,则上述表达式为非零,否则为零。
下面两种写法完全等效:
#ifndef MYHEAD_H
# define MYHEAD_H
程序段1
#endif
//等价于
#if !defined(MYHEAD_H)
#define MYHEAD_H
程序段1
# endif
使用条件编译指令的作用之一是,防止某个头文件被多次包含导致的类和变量的重复定义错误。(出现这种错误的原因是,#include指令是可以嵌套的,即头文件可以嵌套包含)
详见P176