提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


提示:以下是本篇文章正文内容,下面案例可供参考

5.1标识符的作用域与可见性

5.1.1作用域分类:

1.函数原型作用域:

如函数声明:double area (double radius)

2.局部作用域
在这里插入图片描述

3.类作用域

类的成员具有类作用域,其范围包括类体和非内联成员函数的函数体(函数原型在类体中声明,而函数体的实现是在类外,在类外实现的函数体依然是具有类作用域的)

4.命名空间作用域

我们写的小规模程序直接用using namespace std;

std 是 C++ 标准库的命名空间(namespace)。

std 这个命名空间里的所有名字(函数、类、对象等)都引入到当前作用域

这样写之后,你就可以直接写cout而不用写 std::cout

但是大程序不推荐这样用using namespace std;

#include <iostream>
using namespace std;

int count = 10;   // 你定义的变量名 count

int main() {
    cout << count << endl; // 这里 cout 是 std::cout,但 std 里面也有 count 函数
    return 0;
}

这个程序编译器会报歧义错误,命名空间(namespace)就是 C++ 提供的一种解决命名冲突的方法。
语法长这样:

namespace MySpace {
    int a = 42;
    void print() { //定义一个print函数
        std::cout << "a = " << a << std::endl;
    }
}

如果要用 MySpace 里面的东西,可以这样:

int main() {
    MySpace::print();  // 用命名空间限定符 ::
    return 0;
}

也可以局部 using:

int main() {
    using MySpace::a;     // 只引入 a,不引入 MySpace 里的其他东西
    std::cout << a << std::endl;
    return 0;
}

5.限定作用域的枚举类

在这里插入图片描述

5.1.2 可见性

(好抽象的语言描述)
如果某个标识符在外层作用域中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见。
对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。

例题:故意写了两个重名的变量 i

#include<iostream>
using namespace std;

int i;  // 全局变量(没有声明命名空间的,所以全局可用,进入主函数中也是可用的)

int main(){
	i = 5; //为全局变量i赋值 
	{
		int i;  // 局部变量,局部作用域(这个i和全局变量的i名字一样但是是不同的变量,且存储的位置完全不一样的)
		i = 7;
		cout << "i=" << i <<endl;  // 输出7 
	}
	cout << "i=" << i << endl;  // 输出5 
	return 0;
} 

5.2 对象的生存期

5.2.1 静态生存期

静态生存期(比如全局变量):对象的生存期与程序运行期相同
在函数内部(局部作用域中),声明静态生存期对象前面要加上static
局部作用域中的静态变量,不会随着每次函数调用而产生一个新的副本,也不会随着函数返回而失效。也就是说,当一个函数返回后,下次再调用时,该变量还会保持上一回的值,即使发生了递归调用,也不会为该变量建立新的副本。

5.2.2 动态生存期

除了上述两种情况,其余的对象都具有动态生存期

5.3类的静态成员

5.3.1类的静态数据成员

类的静态数据成员是在类的所有对象间共享的,如果在定义类的成员的时候加上关键字static,那么这个成员就是静态的。静态成员在整个类中只有这一份,没有更多的拷贝,静态数据成员具有静态生存期,也就是整个代码运行的过程中,有的对象生成了有的对象消失了,但是静态数据成员始终在那里,当整个程序运行结束,静态数据成员的内存空间才被释放。

静态数据成员一般在类内声明,在类外初始化。C++11支持静态常量(const或constexpr修饰)类内初始化,此时类外仍可定义该静态成员,但不可再次初始化操作。

#include<iostream>

using namespace std;

class Point{
	public:
		Point(int initial_x = 0, int initial_y = 0){ //构造函数 
			x = initial_x;
			y = initial_y;
			count++;
		}
		Point(Point &p){  //复制构造函数 
			x = p.x;
			y = p.y;
			count++;
		} 
		~Point(){   //析构函数 
			count--;
		} 
		int getX(){
			return x;
		}
		int getY(){
			return y;
		}
		void showCount(){
			cout << "Object count = " << count << endl;
		}
	private:
		int x,y;
		static int count; //声明静态数据成员,用于记录点的个数 
}; 
int Point::count = 0; // 静态数据成员定义和初始化,使用类名限定:Point:: 
int main(){
	Point a(4,5);
	cout << "Point A:" << a.getX() << "," << a.getY() << " ";
	a.showCount();
	
	Point b(a); //使用复制构造函数 
	cout <<  "Point B:" << b.getX() << "," << b.getY() << " ";
	b.showCount();
	return 0;
}

运行输出:

Point A:4,5 Object count = 1
Point B:4,5 Object count = 2

当然如果你觉得类中的两个get函数写的很麻烦,也可以不写他们,直接在构造函数中加一个cout,然后主函数创建对象的时候就会自动输出这个点,但是当你想创建多个点的时候,还是要用get函数更合适:

#include<iostream>

using namespace std;

class Point{
	public:
		Point(int initial_x = 0, int initial_y = 0){ //构造函数 
			x = initial_x;
			y = initial_y;
			count++;
			cout << "Point A:" << x << "," << y << "  ";
		}
		Point(Point &p){  //复制构造函数 
			x = p.x;
			y = p.y;
			count++;
			cout << "Point B:" << x << "," << y << "  ";
		} 
		~Point(){   //析构函数 
			count--;
		} 
		void showCount(){
			cout << "Object count = " << count << endl;
		}
	private:
		int x,y;
		static int count; //声明静态数据成员,用于记录点的个数 
}; 
int Point::count = 0; // 静态数据成员定义和初始化,使用类名限定:Point:: 
int main(){
	Point a(4,5);
	a.showCount();
	
	Point b(a); //使用复制构造函数 
	b.showCount();
	return 0;
}

这个代码的输出和上面是一样的,但是为什么复制构造函数也要加上cout呢?难道复制构造函数不是复制的构造函数的所有的内容吗?哈哈哈哈哈我真的太搞笑了。

构造函数和复制构造函数是两个完全独立的函数,都用于创建对象,但使用场景不同:
普通构造函数:当你用参数直接创建新对象时调用,作用是从零开始初始化一个对象:Point a(4,5)
复制构造函数:当你用一个已经存在的对象创建新对象时调用,作用是:用已有对象的成员变量,初始化新对象:Point b(a)
这是两个不同的函数,各自的代码需要单独编写,复制构造函数不会自动复用构造函数的代码!

类的静态函数成员

同样用static来修饰,类外代码可以使用类名和作用域操作符来调用静态成员函数,可以直接使用对象来调用,如果是非静态函数成员,只能通过对象来调用。

#include<iostream>

using namespace std;

class Point{
	public:
		Point(int initial_x = 0, int initial_y = 0){ //构造函数 
			x = initial_x;
			y = initial_y;
			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; // 静态数据成员定义和初始化,使用类名限定:Point:: 

int main(){
	Point::showCount();
	Point a(4,5);
	cout << "Point A:" << a.getX() << "," << a.getY() << "  ";
	a.showCount();
	
	Point b(a); //使用复制构造函数 
	cout << "Point B:" << b.getX() << "," << b.getY() << "  ";
	Point::showCount();  //两种调用方法都可以
	return 0;
}

运行结果:

Object count = 0
Point A:4,5  Object count = 1
Point B:4,5  Object count = 2

在还没有创对象的时候,一个对象还没有,可以使用类名和作用域操作符调用showCount,此时输出是0

5.4 类的友元

通过将一个模块声明为另一个模块的友元,(友元只能在类中声明)比如另一个函数是我的朋友,或者另一个类整个是我的朋友,声明为友元之后,授权给类外的其他函数或者另一个类,可以访问我的私有成员、保护成员。但是要尽量少用或不适用友元。

5.4.1友元函数

在类中用friend修饰声明一个函数原型,在他的函数体中能够通过对象名访问私有成员和保护成员。

5.4.2友元类

若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
在这里插入图片描述

5.5共享数据的保护

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

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

常类型:

(1)常对象:必须进行初始化,不能被更新
const 类名 对象名

(2)常成员:
用const进行修饰的类成员:常数据成员和常函数成员

(3)常引用:被引用的对象不能被更新
const 类型说明符 &引用名

(4)常数组:数组元素不能被更新(详见第六章)
类型说明符 const 数组名[大小]…

(5)常指针:指向常量的指针(详见第六章)

5.5.1常对象:

用const修饰对象

class A{
	public:
		A(int i, int j){
			x=i;
			y=j;
		}
		private:
			int x,y;
}; 
A const a(3,4); //a是常对象,不能被更新 

5.5.2常成员:

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

常对象b只能调用常成员函数void print() const,而不能调用普通成员函数void print(),并且常成员函数里面的代码是不能改变对象的数据成员的。

但是上面的代码中声明的两个函数功能是一样的啊,只是输出r1r2,既然不能删除常成员函数,能不能删除普通成员函数呢?可以的,如果把void print()删掉,那么主函数中对象a.print();就会自动调用常成员函数,这样也是可以的。

//常数据成员举例
#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(){
	/*
	建立对象a1 a2,并以100和0作为初值,分别调用构造函数,
	通过构造函数的初始化列表给对象的常数据成员赋初值 
	*/
	A a1(100),a2(0);
	a1.print();
	a2.print();
	return 0;
}

5.5.3常引用:

如果在声明引用时用const修饰,被声明的引用就是常引用

常引用所引用的对象不能被更新(只读)

如果常引用做形参,便不会意外地发生对实参的更改。声明形式: const 类型说明符 &引用名


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐