深入学习C++——21~23静态

11 篇文章 0 订阅
9 篇文章 1 订阅

深入学习C++——21~23静态

static在C++中有两种含义,分为类和结构体外的静态类和结构体内的静态

21.类和结构体外的静态

类和结构体外部的static,意味着你声明为static的符号只在当前文件内部链接,它只对它所在的翻译单元可见。有关翻译单元请看深入学习C++——6~7编译器和链接器

新建一个源文件命名为CStatic.cpp,在main.cppCstatic.cpp中同时定义一个变量和一个函数,编译器在链接阶段会报错multiple definition of XXX,这是因为这个变量/函数已经在另一个编译单元中定义了。所以我们不能有两个同名的全局变量/函数。其中一种修改方式是使用extern关键字,extern意味着它会在外部翻译单元中寻找这个变量/函数。我们把main.cpp中的定义加上static,再次编译无报错。

/*CStatic.cpp*/
int Variable = 5;
void Function()
{
	std::cout << "Function in CStatic" << std::endl;
}

/*main.cpp*/
//int Variable = 5; //multiple definition of Variable
//void Function() //multiple definition of `Function()'
//{
//	std::cout << "Variable" << std::endl;
//}
extern int Variable;
extern void Function(); //函数默认都是extern,这里删掉extern也可以编译通过
int main()
{
	Function();
	std::cout << Variable << std::endl;
}

另一种解决方法是使用static关键字。static的意思是这个变量/函数只会在这个翻译单元内部链接。这有点像在类中定义私有变量/函数,其他所有的翻译单元都不能看到这个变量/函数。链接器在全局作用域下将不会看到这个变量/函数。把CStatic.cpp中的变量与函数均改成静态的,编译会报错,因为他们在main中不可见,跨翻译单元是找不到他的。将main.cpp中的变量和函数修改为全局的,此时编译通过,且输出的是main中定义的变量值和函数。

/*CStatic.cpp*/
static int Variable = 5;
static void Function()
{
	std::cout << "Function in CStatic" << std::endl;
}

/*main.cpp*/
//extern int Variable; //undefined reference to `Function()'
//extern void Function(); //undefined reference to `Variable'
int Variable = 10;
void Function()
{
	std::cout << "Function in main" << std::endl;
}
int main()
{
	Function();
	std::cout << Variable << std::endl;
}

在类和结构体外使用静态,意味着当你声明静态函数和静态变量时,它只会在它被声明的C++文件中被“看到”。如果在一个头文件中声明静态变量并将该头文件包含在两个不同的C++文件中,就相当于在两个翻译单元中都声明了那个变量为静态变量。(包含头文件时,编译器会复制所有头文件中的内容到C++文件中)

为什么要用static?可以类比为什么要在类中用private。当不需要变量是全局变量时,尽可能地用静态变量。因为一旦在全局作用域下声明东西的时候,编译器会跨编译单元进行链接,这个变量在任何地方都可以被使用,可能会导致一些很严重的bug。

综上,尽可能地标记函数或变量为静态的,除非你真的需要他们跨翻译单元链接。

22.类和结构体内的静态

在类和结构体内部定义的静态变量,在类的所有实例中这个变量只有一个实例,这意味着该变量实际上将与类的所有实例共享内存。对于静态函数来说,没有实例会传递给一个静态函数。

下面用结构体举例子(类也一样,只是因为结构体默认是public的)

struct Entity
{
	int num;
	void Print()
	{
		std::cout << num << std::endl;
	}
};

int main()
{
	Entity e1;
	e1.num = 2;

	Entity e2;
	e2.num = 5;

	e1.Print();
	e2.Print();
}

很显然,这样会输出2和5。如果将结构体内的x和y变成静态的,会报错undefined reference to ‘Entity::num’,因为x和y要在某个地方被定义,加上int Entity::num;,程序修改为:

struct Entity
{
	static int num;
	void Print()
	{
		std::cout << num << std::endl;
	}
};

int Entity::num;

int main()
{
	Entity e1;
	e1.num = 2;

	Entity e2;
	e2.num = 5;

	e1.Print();
	e2.Print();
}

这时运行会输出5和5.这是因为num变量在Entity类的所有实例中只有一个实例,这意味着e1.num和e2.num指向的是相同的内存,所以e1.nume2.num这样写是没有意义的。可以直接写成Entity::num = 5。这就像是在一个名为Entity的命名空间中创建了一个变量,他们实际上并不属于类,但是他们可以是private的也可以是public的,所以他们仍是类的一部分。但是在应用上来说他们其实和在命名空间中一样。

静态方法跟静态变量一样,如果将Print也改为静态的,调用时同样需要使用Entity::Print();

但静态方法不能访问非静态变量,将num改为非静态变量,Print保持为静态方法,这时编译会报错error: invalid use of member 'Entity::num' in static member function,原因是静态方法没有类实例。每个非静态方法总是会获得当前类的一个实例作为参数,这我们是看不见的,在底层通过隐藏参数发挥作用,而静态方法不会得到那个隐藏参数。类中的静态方法拿到类外面在编译的时候实际上是这个样子的,实际上传进去了一个实例参数,这样就不会报错:

struct Entity
{
	int num;
};
static void Print(Entity e)
{
	std::cout << e.num << std::endl;
}

int main()
{
	Entity e;
	e.num = 5;
	Print(e);
}

综上所述,当你需要跨类使用变量时,类内静态变量将会派上用场。那这么说创建一个全局变量或者静态变量不也一样吗?NO!如果你有一条消息,想要在所有实例之间共享数据,把这个消息变量放在类中是有意义的,因为它在逻辑上跟这个类有关。要想组织好你的代码,最好在类中创建一个静态变量,而不是将全局或者静态变量到处乱写。

23.局部静态

局部静态允许我们创建一个变量,它的生存周期基本相当于整个程序的生存期,但是作用范围被限制在这个域中。来看一段程序:

void Function()
{
	int i = 0;
	i++;
	std::cout << i << std::endl;
}
int main()
{
	Function();
	Function();
	Function();
	Function();
	Function();
}

显而易见输出为11111。要是想将i每次递增实现输出12345,你的第一反应可能是将i改为全局变量。但是这样做会使每个人都能访问这个变量,如果要避免这个问题,可以在局部作用域下将i声明为static。这样程序也可以输出12345,与全局变量效果相同,但是此时i只是函数作用域下的局部变量。

使用局部静态的主要作用是可以使代码更干净,我们来看另一个例子。创建一个单例类(单例类是只存在一个实例的类),如果不使用静态局部作用域来创建单例类,就需要创建静态的单例实例,可能是一个指针,并返回一个引用,即为创建的实例:

class Singleton
{
private:
	static Singleton* s_Instance;
public:
	static Singleton& Get()
	{
		return *s_Instance;
	};

	void Hello()
	{
		std::cout << "Hello" << std::endl;
	}
};

Singleton* Singleton::s_Instance = nullptr;

int main()
{
	Singleton::Get().Hello();
}

如果使用局部静态来创建,代码会变得干净很多:

class Singleton
{
public:
	static Singleton& Get()
	{
		static Singleton instance;
		return instance;
	};
	void Hello()
	{
		std::cout << "Hello" << std::endl;
	}
};

int main()
{
	Singleton::Get().Hello();
}

这段代码里如果没有static,当代码运行到函数右花括号处,即函数作用域结束时,instance就会被销毁。通过添加static,它的生存周期被延长到永远,在第一次调用Get的时候,实际上会构造一个单例实例,接下来它只会返回这个已存在的实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
桥接模式是一种结构型设计模式,它将抽象和实现分离,使它们可以独立地变化。桥接模式的核心思想是将一个大类或一组类分解成抽象和实现两个独立的维度,使它们可以独立地变化和扩展,同时通过桥接来将它们连接起来。 在C++中,桥接模式通常通过虚函数实现。抽象部分通过基类定义接口,而实现部分通过派生类实现具体的功能。通过将抽象部分的指针作为参数传递给实现部分的函数,就可以实现两个部分的连接。 下面是一个简单的桥接模式的C++示例: ```c++ class Implementor { public: virtual void operation() = 0; virtual ~Implementor() {} }; class ConcreteImplementorA : public Implementor { public: void operation() override { // 具体的实现A } }; class ConcreteImplementorB : public Implementor { public: void operation() override { // 具体的实现B } }; class Abstraction { public: Abstraction(Implementor* implementor) : m_implementor(implementor) {} virtual void operation() = 0; virtual ~Abstraction() {} protected: Implementor* m_implementor; }; class RefinedAbstraction : public Abstraction { public: RefinedAbstraction(Implementor* implementor) : Abstraction(implementor) {} void operation() override { m_implementor->operation(); // 其他操作 } }; int main() { Implementor* implementorA = new ConcreteImplementorA(); Implementor* implementorB = new ConcreteImplementorB(); Abstraction* abstractionA = new RefinedAbstraction(implementorA); Abstraction* abstractionB = new RefinedAbstraction(implementorB); abstractionA->operation(); abstractionB->operation(); delete abstractionA; delete abstractionB; delete implementorA; delete implementorB; return 0; } ``` 在上面的示例中,Implementor是实现部分的抽象基类,ConcreteImplementorA和ConcreteImplementorB是具体的实现类。Abstraction是抽象部分的基类,RefinedAbstraction是抽象部分的具体实现类。在main函数中,我们创建了不同的Implementor和Abstraction对象,并通过它们来完成不同的操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范子琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值