类的静态成员、类的友元、共享数据的保护、多文件结构

类的静态成员

静态数据成员

  • 用关键字static在声明(声明都是在类体内完成的)
  • 为该类的所有对象共享,静态数据成员具有静态生存期
  • 必须在类外定义初始化,用::来指明所属的类。
    • 静态数据成员在类外定义和初始化是有原因的,主要目的是为了灵活设定静态数据的初始值。

静态函数成员

  • 类外代码可以使用类名和作用域限定符来调用静态成员函数
  • 静态成员函数主要用来处理该类的静态成员数据,可以直接调用静态成员函数。
  • 如果访问非静态成员函数,可以通过对象来访问。
#include <iostream>
using namespace std;
class Point{  // Point类定义
public:		// 外部接口
    Point(int xx=0,int yy=0):x(xx),y(yy){ // 构造函数
        // 在构造函数中对count累加,所有对象共同维护同一个count
        count++;
    }
    Point(Point &p){ \\ 拷贝构造函数
        x = p.x;
        y = p.y;
        count++;
    }
    ~Point(){count--;}
    int getX(){return x;}
    int getY(){return y;}
    static void showCount(){		// 输出静态数据成员
        cout << " Object count = " << count << endl;
    }
    
private:	// 私有数据成员
    int x,y;
    static int count; // 静态数据成员的声明,用于记录点的个数
};
int Point::count = 0; // 静态数据成员定义和初始化,使用类名限定
int main(){
    Point a(4,5); // 定义对象a,其构造函数会使count加1
    count << "Point A: " << a.getX() << ", " << a.getY();
    Point::showCount(); // 输出对象个数   也可以通过a.showCount(); 来调用静态成员函数
    
    Point(a);  // 定义对象b,其拷贝构造函数会使count增1
    cout << "Point B: " << b.getX() << ", " << b.getY();
    Point::showCount(); // 输出对象个数
    
    return 0;
}

类的友元

  • 友元是C++提供的一种破坏数据封装数据隐藏的机制。
  • 通过将一个模块A声明为另一个模块B的友元,则模块A能够引用到模块B中本是被隐藏的信息。
  • 类的友元可以分为:友元函数和友元类。

为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。

友元函数

  • 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 privateprotected成员

作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。

  • 友元函数访问对象中的成员必须通过对象名

示例: 使用友元函数计算两点间的距离

#include <iostream>
#include <cmath>

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; }
    
	friend float dist(Point &a, Point &b); // 友元函数

private: //私有数据成员
int x, y;
};

float dist( Point& a, Point& b) {
	double x = a.x - b.x;
	double y = a.y - b.y;
	return static_cast<float>(sqrt(x * x + y * y));
}

int main() {

	Point p1(1, 1), p2(4, 5);
	cout <<"The distance is: ";
	cout << dist(p1, p2) << endl;

	return 0;
}

友元类

  • 若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。

声明语法:将友元类名在另一个类中使用friend修饰说明。

class A {

	friend class B; // 类中友元函数的声明可以放在任何地方,不受public、protected和private的管辖和限制

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.x=i;
}

void B::display() {
	a.display();
};

注意:类的友元关系是单向的

共享数据的保护

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

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

常类型

  • 常对象:必须进行初始化,不能被更新。
    • 通过常对象只能调用它的常函数成员。常对象不可调用非常成员函数
    • 定义格式:const 类名 对象名类名 const 对象名
  • 常成员:用const进行修饰的类成员:
    • 常数据成员
    • 常函数成员:不更新对象的数据成员
      • 类型说明符 函数名(参数表)const;这里,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是常对象,不能被更新
常成员函数举例
#include<iostream>

using namespace std;

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;
}
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;
}
常数据成员举例

为什么要在构造函数中完成对常数据成员的初始化赋值操作?

因为常数据成员只能赋值一次,当对象被创建时,会自动调用构造函数,对成员数据进行赋初值,如果不指定,通常被赋值为0,而0不一定时我们想要的数值。

#include <iostream>

using namespace std;

class A {
public:
	A(int i);
    void print();

private:
	const int a;
    static const int b;  //静态常数据成员,必须在类外定义和初始化,且类外定义不能有static关键字
};

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;
}
常引用举例

常引用作形参

  • 如果用常引用做形参,便不会意外地发生对实参的更改。这要既能高效的访问引用对象,又能保证被引用对象的安全
#include <iostream>
#include <cmath>

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; }

    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);    
	cout << "The distance is: ";
	cout << dist(myp1, myp2) << endl;

    return 0;
}

如何理解初始化列表???

多文件结构

声明和定义的重新理解

声明定义是两码事,声明仅仅是给编译器作提示用,定义是编译器实实在在地把内容转换成二进制代码,分配内存空间,作为可执行的特定指令。所以声明可以有多处,但是同一内容却只能定义一次以避免定义二义性和模糊性。用extern就是明确告诉编译器这是在声明,而不是定义

对于函数声明,加不加extern都行,因为函数的声明后面会跟一个分号,很容易辨识。但是,基本变量的定义和声明在不加extern时,都会被对待为定义,所以要想使用其他文件的全局变量,必须在引用的文件中用extern声明

外部变量

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

外部函数

  • 在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。
  • 这样的函数可以在不同的编译单元中被使用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型时用extern修饰,其效果与不加修饰的默认状态是一样的。

将变量和函数限制在编译单元内

  • 使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其他的编译单元。
 namespace{  // 被namespace{...}括起来的区域都属于匿名的命名空间。
     int n;
     void f(){
         n++;
     }
 }

多文件结构的编译过程

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值