一、标识符的作用域和可见性
(一)作用域
1、全局作用域
在函数外部声明的变量和函数具有全局作用域。这些变量和函数在程序的任何地方都可以被访问。
2.局部作用域
在函数内部、循环体内部或条件语句内部声明的变量具有局部作用域。这些变量只能在其声明的代码块内部被访问。
#include <iostream>
void localScopeFunc() {
int localVar = 20; // 局部变量
std::cout << localVar << std::endl; // 输出: 20
}
int main() {
localScopeFunc();
// std::cout << localVar << std::endl; // 错误!localVar 在此处不可见
return 0;
}
3、类作用域
在类中声明的成员变量和成员函数具有类作用域。它们只能通过类的对象或指针来访问。
#include <iostream>
class MyClass {
public:
int x;
void printX() {
std::cout << "x = " << x << std::endl;
}
};
int main() {
MyClass obj;
obj.x = 10;
obj.printX(); // 输出: x = 10
return 0;
}
4、命名空间作用域(Namespace Scope)
一个大型的程序通常由不同模块构成,不同的模块甚至有可能是由不同人员开发的。不同模块中的类和函数之间有可能发生重名,这样就会引发错误。这就好像上海和武汉都有南京路,如果在缺乏上下文的情况下直接说出“南京路”,就会产生歧义。但如果说“上海的南京路”或“武汉的南京路”,歧义就会消除。命名空间起到的就是这样的作用。
#include <iostream>
namespace CD {
class clock {
public:
int x;
clock() : x(30) {} // 构造函数中初始化x
void namespaceFunc() {
std::cout << "Function in CD called." << std::endl;
}
};
} // namespace CD
namespace xian {
class clock {
public:
int x;
clock() : x(30) {} // 构造函数中初始化x
void namespaceFunc() {
std::cout << "Function in xian called." << std::endl;
}
};
} // namespace xian
int main() {
CD::clock cdClock;
std::cout << cdClock.x << std::endl; // 输出: 30
cdClock.namespaceFunc(); // 调用CD命名空间中的函数
xian::clock xianClock;
std::cout << xianClock.x << std::endl; // 输出: 30
xianClock.namespaceFunc(); // 调用xian命名空间中的函数
return 0;
}
也可以在main函数中
在上述示例中都是std::XXX的情况是由于没有在最开头提出命名空间域 using namespace std
(二)可见性
作用域之间是可以相互包含的关系
- 标识符要声明在前,引用在后:
- 在使用任何标识符之前,必须先声明它。这意味着你必须在代码中先定义变量、函数或其他标识符,然后才能使用它们。
- 在同一作用域中,不能声明同名的标识符:
- 在同一个作用域内,你不能声明两个具有相同名称的标识符。例如,在同一个函数内,你不能声明两个同名的局部变量。
- 在没有互相包含关系的不同的作用域中声明的同名标识符,互不影响:
- 如果两个标识符在不同的作用域中声明,并且这些作用域之间没有包含关系,那么这两个标识符是独立的,互不影响。例如,一个函数内的局部变量和一个全局变量可以有相同的名称,它们不会互相干扰。
- 如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见:
- 如果一个标识符在一个作用域内被声明,并且在另一个包含它的作用域内也被声明(同名),那么内层作用域中的标识符会隐藏外层作用域中的同名标识符。这意味着在内层作用域中,你只能访问内层声明的标识符,而不能访问外层声明的同名标识符。
二、生存期
1、静态生存期
定义:静态生存期指的是对象的生存期和程序的运行期相同,即只要程序开始运行(主程序运行之),该对象就被分配了内存,并且在程序结束之前始终存在。
特点:
- 持久性:具有静态生存期的对象在程序的整个运行期间都存在,不会被自动销毁。
- 作用域:虽然静态生存期与程序的运行期相同,但静态对象的作用域可能受到声明位置的影响。例如,在全局作用域中声明的静态
- 对象具有全局可见性,而在函数内部声明的静态局部变量则仅在函数内部可见。
- 初始化:静态对象通常只初始化一次,无论它们被访问多少次。对于静态局部变量,初始化发生在函数第一次调用时,之后即使函数多次被调用,静态局部变量也不会被重新初始化。
实现方式:
- 在全局作用域中声明的对象默认为静态生存期。
- 在函数内部或局部作用域中,可以使用
static
关键字显式声明静态对象。
2、动态生存期
定义:动态生存期指的是对象的生命周期是动态的,可以在程序运行时根据需要创建和销毁。
特点:
- 灵活性:动态生存期的对象可以根据程序的需要在运行时创建和销毁,提供了更高的灵活性。
- 作用域:动态生存期的对象的作用域通常限于创建它们的代码块或函数中。
- 手动管理:与静态生存期对象不同,动态生存期对象需要程序员手动管理其生命周期,包括创建和销毁。这通常涉及内存分配和释放操作,如使用
new
和delete
操作符(在C++中)或malloc
和free
函数(在C中)。
3、举例
(1)在全局上声明Text 类的全局变量mit;
(2)用static修饰类Test的变量t,使其在整个程序结束时才销毁。
就算调用两次 foo()也只创建销毁一次因为是静态生存期内的。
如果t时动态生存期内的话
(3)未被初始化的静态生存期会被置零。
就算是成员变量未被初始化,在定义一个静态生存期的变量后,也是会被置零。
(4)匿名对象是动态生存期
当对象被使用完之后会立马被销毁
可以给他个别名(匿名对象的引用需要给他一个const),导致其生存期变长了,
但是这样无法调用showtime()函数【这个函数在clock类中】这个涉及到右值引用的问题
左值和右值
lvalue-locatable
rvalue -readable
在C++中,左值(lvalue)和右值(rvalue)是表达式属性的一部分,它们决定了表达式如何使用和它们可以出现在哪些上下文中。
左值:可以出现在赋值语句的左侧,表示内存中的一个位置。左值可以是变量、数组元素、结构体成员、函数返回值(如果该函数返回引用或指针)等。左值具有持久的状态,可以被多次读取和修改。
右值:不能出现在赋值语句的左侧,表示一个临时的值。右值可以是字面量、表达式的结果等。右值通常是匿名的,没有持久的状态,读取后就会被销毁。
在C++11及更高版本中,引入了右值引用(rvalue reference),用
&&
表示,它允许我们引用右值,从而可以延长右值的生命周期或对其进行操作,而不会立即销毁它们。、
判断一个是左值还是右值,对其取地址,如果可以取地址就是可以定位的,即左值,
无法取地址就是无法取对,即右值
在C++中,所有的匿名变量都是右值。
左值
int i;
右值:
const int i;
clock(10,10,10);
所以 const clock &ret =const(10,10,10)
ret.settime();
无法继续进行,settime()需要的是可读可写的左值,我们给的是只读的右值
const clock &ret = clock(10, 10, 10); ret.settime(); // 错误:ret是一个指向const的引用,指向一个右值,该右值在表达式结束后会被销毁。
这里的
clock(10, 10, 10)
是一个右值,试图将它绑定到一个const引用上。虽然这在语法上是允许的,但由于这个右值在表达式结束后就会被销毁,所以你不能对它进行任何修改操作,即使是通过const引用也不行(在这里的尝试是调用
settime()
方法,这显然是想要修改对象)解决办法
const clock &&ret=clock(10,10,10)
将其变成了一个右值引用的办法,使其可以引用。
clock &&ret = clock(10, 10, 10); ret.settime(); // 正确:ret是一个右值引用,它延长了临时对象的生命周期。
样,
ret
就成了一个指向右值的引用,它允许我们修改这个右值,并且由于右值引用的特性,这个右值的生命周期被延长到了与ret
相同的生命周期。这就是C++11中引入右值引用的主要目的之一:允许我们更有效地处理临时对象,特别是与移动构造函数和移动赋值运算符相关的场景。
三、类的静态成员
类的静态成员是类级别(而非实例级别)的共享成员,它属于类本身而不是类的任何特定实例。静态成员包括静态变量(也称为类变量)和静态方法。静态成员的特点是在所有实例之间共享,并且可以在没有创建类实例的情况下通过类名直接访问。没有对象情况下直接就可以调用
静态成员变量的声明方法:
static 数据类型 成员变量名;
#include<iostream>
using namespace std;
class Data
{
public:
int n;
static int m; //静态成员变量 类内声明
};
int Data::m=0; //类外初始化
int main()
{
Data d1,d2;
return 0;
}// d1.n 与 d2.n 是两个不同的变量
// d1.m 与 d2.m 是同一个变量
静态成员变量的初始化
数据类型 类名::静态成员变量 = 初始值;
#include<iostream>
using namespace std;
class Data
{
public:
int n;
static int m; //静态成员变量 类内声明
};int Data::m=0; //类外初始化
int main()
{
Data d1,d2;
return 0;
}
(一)静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。
#include <iostream>
using namespace std;
class MyClass
{
public:
MyClass(int a, int b, int c);
void fun();
private:
int a,b,c;
static int sum;//声明静态数据成员
};
int MyClass::sum = 0;
MyClass::MyClass(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
sum += a+b+c;
}
void MyClass::fun()
{
cout<<"sum = "<<sum<<endl;
}
void main()
{
MyClass M(1,2,3);
M.fun();
MyClass N(4,5,6);
N.fun();
}
从以上的程序可以看出,静态数据成员有以下特点:
- 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。
无论这个类的对象定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。
即静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新。
静态数据成员和普通数据成员一样遵从public、private、protected访问规则。
由于静态数据成员在全局数据区分配内存,属于本类的所有对象共享,它不属于特定的类对象,在没有产生类对象时作用域就可见。即在没有产生类的实例时,我们就可以操作它。
静态数据成员的初始化与一般数据成员的初始化不同,即它的初始化格式为:
<数据类型><类名>::<静态数据成员> = <值>
类的静态数据成员有两种访问方式:
如果静态数据成员的访问权限允许的话,即为public成员,可在程序中,按上述格式来引用静态数据成员;
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对一个存款类,每个实例的利息都是相同的,所以把利息可以设为存款类的静态数据成员。
这有两个好处,
一是不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省了存储空间。
二是一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了。
同全局变量相比,使用静态数据成员有两个优势:
(1)命名空间问题:静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性;
(2)封装问题:可以实现信息隐藏。静态数据成员可以使private成员,而全局变量不能。
上述例子中修改s1,s2不会跟着变。
其中的static string companyName即静态数据成员
对于类的静态成员的访问可以使用类名+双冒号来访问
做到就算没有成员变量,也可以来访问内部数据。
访问私有成员:
(二)类的静态函数成员
静态成员函数的声明
声明方法:
static 返回值类型函数名(参数表)
//在书写静态成员函数的代码时,不必写static ## static:声明要,定义不要
#include<iostream>
using namespace std;
class Student
{
int id;
float score;
static float sum; //总分
static int count; //计数
public:
Student(int i,int s):id(i),score(s) //定义构造函数
{
count++;
}
void total(); //总分
static float average(); //平均数 //静态成员函数的声明
};
float Student::sum = 0; //静态成员变量的初始化
int Student::count = 0;void Student::total()
{
sum += score;
}float Student::average() //静态成员函数
{
if(count)
return sum/count;
}int main()
{
Student stu[3] = {Student(1,10) , Student(2,20) , Student(3,30)};
for(int i=0;i<3;i++)
stu[i].total();
cout<<"平均分是:"<<Student::average(); //不需要用对象,因为这是共有的
return 0;
}
说明:静态成员函数可以直接引用私有的静态数据成员(不需要加类名或者对象名)
建议:只用静态成员函数引用静态数据成员,而不引用非静态数据成员。这会使思路清晰,不易出错
2、静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务,而不是为某一个类的具体对象服务。
静态成员函数与静态数据成员一样,都是在类的内部实现,属于类定义的一部分。
普通的成员函数一般都隐藏了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。
通常情况下,this指针是缺省的、但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针,从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。
Example 6
#include <iostream>
using namespace std;
class MyClass
{
public:
MyClass(int a, int b, int c);
static void fun();
private:
int a,b,c;
static int sum;//声明静态数据成员
};
int MyClass::sum = 0;
MyClass::MyClass(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
sum += a+b+c;
}
void MyClass::fun()
{
cout<<"sum = "<<sum<<endl;
}
void main()
{
MyClass M(1,2,3);
M.fun();
MyClass N(4,5,6);
N.fun();
MyClass::fun();//静态成员函数的访问
}
关于静态成员函数,可以总结以下几点:
(1)出现在类体外的函数不能指定关键字static;
(2)静态成员之间可以互相访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
(3)非静态成员函数可以任意地访问静态成员函数和静态数据成员;
(4)静态成员函数不能访问非静态成员函数和非静态数据成员;
(5)由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比,
速度上会有少许的增长;
(6)调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指调用静态成员函数。
(三)举例子
创建一个类可以统计创建对象的个数。
#include <iostream>
using namespace std;
class Demo
{
public:
Demo()
{
counter++;
}
~Demo()
{
counter--;
}
static int getCounter(); // 修正函数名以匹配变量名
private:
static int counter; // 修正变量名以匹配构造函数和析构函数中的使用
};
int Demo::counter = 0; // 显式初始化静态成员变量
int Demo::getCounter()
{
return counter;
}
int main(void)
{
cout << "Before creating instances: " << Demo::getCounter() << endl;
Demo d1, d2, d3;
cout << "After creating 3 instances: " << Demo::getCounter() << endl;
// 当d1, d2, d3超出作用域时,它们的析构函数将被调用
// 但由于main函数结束时整个程序都会结束,所以这里可能不会立即看到析构函数的效果
return 0;
}
如果不希望有复制构造函数的话,将复制构造函数设为私有。
#include <iostream>
using namespace std;
class Demo {
public:
Demo() {
counter++; // 构造函数,每次创建新对象时增加计数器
cout << "Constructor called" << endl;
}
// 将复制构造函数设为私有
Demo(const Demo& other) {
cout << "Copy Constructor called" << endl;
// 注意:在实际禁止复制的场景中,这里可能不需要做任何操作,或者抛出一个异常
}
~Demo() {
counter--; // 析构函数,每次销毁对象时减少计数器
cout << "Destructor called" << endl;
}
static int getCounter();
private:
static int counter;
// 禁止赋值操作(可选,根据需求)
Demo& operator=(const Demo& other) = delete;
};
int Demo::counter = 0;
int Demo::getCounter() {
return counter;
}
int main(void) {
cout << "Before creating instances: " << Demo::getCounter() << endl;
Demo d1;
// 由于复制构造函数是私有的,以下行将导致编译错误
// Demo d2 = d1; // 这里会调用复制构造函数,但现在它是私有的
// 如果确实需要类似复制的行为,可以考虑提供一个克隆函数(不是复制构造函数)
cout << "After creating instance: " << Demo::getCounter() << endl;
// 当d1超出作用域时,它的析构函数将被调用
return 0;
}
四、设计模式其一
目录
一、标识符的作用域和可见性
(一)作用域
1、全局作用域
2.局部作用域
3、类作用域
4、命名空间作用域(Namespace Scope)
(二)可见性
二、生存期
1、静态生存期
2、动态生存期
3、举例
(1)在全局上声明Text 类的全局变量mit;
(2)用static修饰类Test的变量t,使其在整个程序结束时才销毁。
(3)未被初始化的静态生存期会被置零。
(4)匿名对象是动态生存期
三、类的静态成员
(一)静态数据成员
从以上的程序可以看出,静态数据成员有以下特点:
静态数据成员存储在全局数据区,静态数据成员定义时要分配空间,所以不能在类声明中定义。应该在类外定义。
这有两个好处,
同全局变量相比,使用静态数据成员有两个优势:
静态成员函数的声明
2、静态成员函数
关于静态成员函数,可以总结以下几点:
四、设计模式其一
五、常成员函数:
五、友元函数
2,类做友元
23 种设计模式详解(全23种)_23种设计模式-CSDN博客
单例模式实现
是一种常用的软件设计模式,用于确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。这种模式在需要控制资源访问(如数据库连接、文件句柄等)或者实现配置信息的全局访问时非常有用。
单例模式的实现通常包含以下几个关键要素:
私有构造函数:防止外部代码通过
new
关键字创建类的实例。私有静态变量:用于存储类的唯一实例。
公有静态方法:提供一个全局访问点来获取类的唯一实例。该方法在第一次被调用时创建实例,并在后续调用时返回已创建的实例。
确保线程安全(可选):在多线程环境下,需要确保单例的创建过程是线程安全的,以避免多个线程同时创建多个实例。
#include <iostream>
class Singleton {
public:
static Singleton* getInstance() {
if (m_p == NULL) {
m_p = new Singleton;
}
return m_p;
}
static void destroyInstance() {
delete m_p;
m_p = NULL;
}
static int getCounter() {
return counter;
}
private:
Singleton() {
counter++;
}
~Singleton() {
counter--;
}
static Singleton* m_p;
static int counter;
};
Singleton* Singleton::m_p = NULL;
int Singleton::counter = 0;
int main(void) {
Singleton* p = Singleton::getInstance();
std::cout << Singleton::getCounter() << std::endl; // 应该输出 1
Singleton* q = Singleton::getInstance();
std::cout << p << std::endl;
std::cout << q << std::endl; // 如果两个地址相同,则说明是同一个实例
// 注意:在实际应用中,您可能希望在程序结束前调用 destroyInstance()
// 但在这个简单的示例中,我们省略了这一步,因为当程序结束时,操作系统会回收所有资源
return 0;
}
五、常成员函数:
在C++中,常成员函数(也称为常量成员函数)是一种特殊的成员函数,它承诺不会修改类的任何成员变量(静态成员变量除外,因为它们不属于类的任何特定对象)。常成员函数通过在函数声明后加上const
关键字来标识。
1.常成员函数声明:
const成员函数也就是常成员函数,它的声明形式:
返回类型 成员函数名(参数表) const ;
例如: int function(int x) const ;
易混淆的地方:
const 的位置:
函数开头的 const 用来修饰函数的返回值,表示返回值是 const 的,也就是不能被修改,
例如const char * getname()。
函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const。
2.常成员函数的主要特点:
1)不能更新类的成员变量
2)不能调用该类中没有用const修饰的成员函数,即只能调用常成员函数
3)可以被类中其它的成员函数调用
4)常对象只能调用常成员函数,而不能调用其他的成员函数
例如在clock类中:
显示时间得函数是不需要修改任何参数得。
下面是另外一个例子:
class CDate
{
public:
//常成员函数
int year() const{ return y; }
int month() const{
//m++; //错误:1)常成员函数中不能更新类的数据成员
return m;
}
//普通成员函数
int day(){return d++ ;}
int AddYear(int i){ return y+1; }
private:
int y;
int m;
int d;
};
int main()
{
CDate const date; //常对象
//int day = date.day(); // 4)错误常对象不能调用非常成员函数
int year = date.year(); //正确
}
class A{
private:
int w,h;
public:
int getValue() const{ //常成员函数
return w*h + getValue2(); //2)错误。只能调用常成员函数。
}
int getValue2(){ //普通成员函数
return w+h+getValue(); //3)正确。常成员函数可以被其它成员函数调用
}
A(int x,int y){
w=x,h=y;
}
A(){}
};
六、友元函数
参考:https://blog.csdn.net/weixin_46098577/article/details/116596183
生活中你的家有客厅(public),有你的卧室(private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许 隔壁老王 进去。
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的 就是让一个函数或者类 访问另一个类中的私有成员
友元的关键字为 friend
友元的三种实现
全局函数做友元
类做友元
成员函数做友元
(1)全局函数做友元
首先,我们要定义一个房屋类,公共成员变量为客厅,私有成员变量为卧室
class Building
{
// Building的构造函数,给成员变量赋初值
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom; // 客厅private:
string m_BedRoom; // 卧室
};
然后定义一个全局函数 laoWang(),用来访问Building类中的私有成员
void laoWang1(Building *building)
{
cout << "隔壁老王 全局函数 正在访问:(地址传递) " << building->m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:(地址传递) " << building->m_BedRoom << endl;
}
当然也可以用引用传递或者最简单的值传递
void laoWang2(Building &building)
{
cout << "隔壁老王 全局函数 正在访问:(引用传递) " << building.m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:(引用传递) " << building.m_BedRoom << endl;
}void laoWang3(Building building)
{
cout << "隔壁老王 全局函数 正在访问:( 值传递 ) " << building.m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:( 值传递 ) " << building.m_BedRoom << endl;
}
最后定义一个测试函数test(),实现 laoWang() 这个全局函数做友元访问类的私有成员
void test()
{
Building building;
laoWang1(&building);
laoWang2(building);
laoWang3(building);
}
但是,现在还不能实现全局函数访问类的私有成员!
关键代码
friend void laoWang1(Building *building);
friend void laoWang2(Building &building);
friend void laoWang3(Building building);
在Building类中声明友元函数,告诉编译器 laoWang全局函数是 Building类 的好朋友,可以访问Building对象的私有成员
#include <iostream>
#include <string>using namespace std;
// 房屋类
class Building
{
// 告诉编译器 laoWang全局函数是 Building类 的好朋友,可以访问Building对象的私有成员
friend void laoWang1(Building *building);
friend void laoWang2(Building &building);
friend void laoWang3(Building building);public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom; // 客厅private:
string m_BedRoom; // 卧室
};//全局函数
void laoWang1(Building *building)
{
cout << "隔壁老王 全局函数 正在访问:(地址传递) " << building->m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:(地址传递) " << building->m_BedRoom << endl;
}void laoWang2(Building &building)
{
cout << "隔壁老王 全局函数 正在访问:(引用传递) " << building.m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:(引用传递) " << building.m_BedRoom << endl;
}void laoWang3(Building building)
{
cout << "隔壁老王 全局函数 正在访问:( 值传递 ) " << building.m_SittingRoom << endl;cout << "隔壁老王 全局函数 正在访问:( 值传递 ) " << building.m_BedRoom << endl;
}void test()
{
Building building;
laoWang1(&building);
laoWang2(building);
laoWang3(building);
}
int main()
{
test();
}
(2)类做友元
首先,声明一个要访问的私有变量所属的Building类,防止在下面的好LaoWang类中,编译器不认识Building(当然也可以采取先定义Building类,再定义隔壁老王LaoWang类,这样就不用声明Building类了)
#include <iostream>
#include <string>using namespace std;
// 类作友元
class Building;
class LaoWang
{
public:LaoWang();
void visit(); //参观函数 访问Building中的属性
Building * building;
private:
};// 房屋类
class Building
{
// 告诉编译器,LaoWang类是Building类的好朋友,可以访问Building类的私有成员
friend class LaoWang;
public:
Building();
string m_SittingRoom; // 客厅
private:
string m_BedRoom; // 卧室
};// 类外定义成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}LaoWang::LaoWang()
{
// 创建建筑物对象
building = new Building;
}void LaoWang::visit()
{
cout << "隔壁老王LaoWang类正在访问:" << building->m_SittingRoom << endl;cout << "隔壁老王LaoWang类正在访问:" << building->m_BedRoom << endl;
}void test()
{
LaoWang lw;
lw.visit();
}int main()
{
test();return 0;
}
3 成员函数做友元
#include <iostream>
#include <string>using namespace std;
class Building;
class LaoWang
{
public:LaoWang();
void visit1(); //让visit1()函数 可以 访问Building中的私有成员
void visit2(); //让visit2()函数 不可以 访问Building中的私有成员Building *building;
private:
};class Building
{
// 告诉编译器,LaoWang类下的visit1()函数是Building类的好朋友,可以访问Building的私有成员
friend void LaoWang::visit1();public:
Building();string m_SittingRoom; //客厅
private:string m_BedRoom; //卧室
};
LaoWang::LaoWang()
{
building = new Building;
}void LaoWang::visit1()
{
cout << "隔壁老王LaoWang类中的visit1()函数正在访问:" << building->m_SittingRoom << endl;
cout << "隔壁老王LaoWang类中的visit1()函数正在访问:" << building->m_BedRoom << endl;
}void LaoWang::visit2()
{
cout << "隔壁老王LaoWang类中的visit2()函数正在访问:" << building->m_SittingRoom << endl;
//cout << "隔壁老王LaoWang类中的visit2()函数正在访问:" << building->m_BedRoom << endl; //错误!私有属性不可访问
}Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}void test()
{
LaoWang lw;
lw.visit1();lw.visit2();
}int main()
{
test();
return 0;
}
七 volatile禁止优化关键字
在C++中,volatile
关键字确实用于指示编译器该变量的值可能会以编译器无法预见的方式被改变。这主要用于访问硬件寄存器、内存映射的I/O地址、多线程程序中被其他线程修改的共享数据,或者在中断服务例程(ISR)中可能被修改的变量等场景。使用volatile
关键字可以阻止编译器对这些变量进行优化,如缓存变量的值或对其进行不必要的重排序。
在第一个函数中,变量i
被声明为一个整型常量,并赋值为100。然后,p
变量被初始化为指向i
的指针,并设置为1000。接下来,在第二个函数中,同样声明了变量li
并赋值为100。
然而,这里的p
变量被初始化为指向li
的指针,并设置为1000。
最后,两个函数都使用了cout
来输出变量i
的值。需要注意的是,虽然这两个函数的输入参数是相同的,但它们的结果不同。因为第一个函数返回的是原始的i
值,而第二个函数返回的是通过指针访问到的li
值。
八、强制类型转换
static_cast
- 用于在具有明确转换关系(如基类和派生类之间的转换、基本数据类型之间的转换等)的类型之间进行转换。
- 可以在一定程度上提供编译时检查,增加代码的安全性。
- 示例:将派生类指针或引用转换为基类指针或引用(向上转换)或基类指针转换为派生类指针(向下转换,但不安全,除非已经通过其他方式(如
dynamic_cast
)确认了类型)。
class Base {
public:
virtual ~Base() {}
void show() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void display() { std::cout << "Derived" << std::endl; }
};
int main() {
Derived* d = new Derived();
Base* b = static_cast<Base*>(d); // 向上转换,安全
b->show(); // 输出: Base
delete d;
}
reinterpret_cast
- 几乎可以转换任何类型的指针(或引用)到其他类型的指针(或引用),甚至包括不相关的类型。
- 它主要用于底层编程和与C语言代码的互操作,如处理函数指针或转换指针类型以符合特定硬件要求。
- 几乎不进行类型检查,因此使用时需要格外小心。
class Base {
public:
virtual ~Base() {}
void show() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void display() { std::cout << "Derived" << std::endl; }
};
int main() {
Derived* d = new Derived();
Base* b = static_cast<Base*>(d); // 向上转换,安全
b->show(); // 输出: Base
delete d;
}
const_cast:
去原来的const属性
- 用于去除类型的
const
、volatile
或const volatile
限定符。 - 通常用于修改由函数返回的常量指针或引用所指向的数据(注意,这只有在你确定这样做是安全的情况下才应该进行)。
- 示例:将
const int*
转换为int*
以修改所指向的值。 -
void modifyConstInt(const int* ptr) { int* nonConstPtr = const_cast<int*>(ptr); // 去除const属性 *nonConstPtr = 20; // 修改值,注意这里需要确保原始数据不是const的 } int main() { int value = 10; modifyConstInt(&value); // 传递非const变量的地址 std::cout << value << std::endl; // 输出: 20 }
dynamic_cast
- 主要用于类的层次结构中的安全向下转换(将基类指针或引用转换为派生类指针或引用)。
- 在运行时检查转换的安全性,如果转换不合法,则转换失败(对于指针,转换结果为
nullptr
;对于引用,转换失败会抛出std::bad_cast
异常)。 - 适用于含有虚函数的类之间的转换,因为
dynamic_cast
依赖于运行时类型信息(RTTI)。 -
class Base { public: virtual ~Base() {} virtual void show() { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void display() { std::cout << "Derived" << std::endl; } }; int main() { Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // 向下转换,运行时检查 if (d) { d->display(); // 输出: Derived } delete b; }