day002.zip
[TOC]
静态成员
- 静态成员变量
C++中作用域:- 复合语句 \ 局部作用域
- 函数作用域 /
- 文件作用域 - 全局作用域
- 类作用域
- 命名空间
静态成员变量是在类内定义的, 但它本质上属于全局变量. 但是它的作用域是属于类域.
意思是:
- 静态成员变量的生存周期和全局变量是一样(从程序运行到程序结束)(成员变量的生存周期是和对象一起. 对象被构造出来,成员变量拥有内存, 对象被析构,成员变量就是去内存. 而对象又分为局部对象,全局对象,堆空间对象), 因此静态成员变量不属于对象中的一部分(也就是说对象的内存中不包含静态成员变量)
- 静态成员变量的作用域是属于类域(影响着对静态成员变量的使用.), 当需要在类外使用静态成员变量的时候, 可以通过两种方法来访问:
- 通过类名, 例如:
类名::静态成员变量名
- 通过对象, 例如:
对象.静态成员变量名
```c class MyClass{
- 通过类名, 例如:
public:
// 1. 声明静态成员变量(加一个static关键字)
static int m_nNum;
};
// 2. 必须要在类外定义静态成员变量
// 需要在变量名前加上作用域说明该变量属于类的.
// 如果没有定义, 就会报错: 无法解析的外部符号m_nNum
int MyClass::m_nNum;
int g_nNum;
int main()
{
// 使用静态成员变量
MyClass::m_nNum3 = 10;
// 比较两者的地址,就会发现它们在相近的内存中.
printf(“g_nNum=%p m_nNum3=%p\n”,
&g_nNum,
&MyClass::m_nNum3 );
}
2. 静态成员函数
```c
class MyClass{
int m_nNum1;
static int m_nNum;
public:
//
// 定义静态成员函数:加上static关键字
// 1. 静态成员函数的本质相当于类外的一个普通
// 函数(在函数内部没有this指针)
// 2. 静态成员函数的作用域属于类的作用域(在
// 静态成员函数内部, 通过类对象或对象指针
// 可以访问到全部的成员变量和成员函数)
static void staticFun() {
//this->m_nNum1 = 0;// 1. 静态成员函数没有this指针
MyClass obj;
obj.m_nNum1 = 0;// 2.通过对象可以访问到私有成员
m_nNum3 = 100; // 3. 当然也可以直接访问静态成员变量
}
};
int MyClass::m_nNum = 0;
int main()
{
// 调用静态成员函数:
MyClass::staticFun();
}
友元
- C++提供了三大访问控制权限用于控制类外,类内,子类内对成员的访问的控制.
- 友元就是一个类对某个对象授予所有的访问控制权限
- 例如
MyClass
有一个私有的成员变量m_nNum
- 在main函数中是不能直接通过对象来访问的. 但是通过友元授权,main也能直接通过对象来访问到这个类的所有成员.
- 例如
- 友元能够授予权限的对象:
- 友元普通函数 : 将类的访问权限全部授予一个普通的函数 ```c class MyClass{ int m_nNum; static int m_staticNum; // 使用friend关键字将某个普通函数的函数头放在类内声明 // 这个普通函数就能成为本类友元普通函数. friend int main(); }; int MyClass::m_staticNum;
int main(){ MyClass obj; obj.m_nNum=0;// 成为友元之后可以访问私有成员 MyClass::m_staticNum=0; }
1. 友元类 : 将本类的访问权限全部授予给另一个类(在另一个类的所有成员函数中都能直接访问到授权类的所有成员)
```c
class MyClass{
int m_nNum;
static int m_staticNum;
// 声明一个友元类: friend class 类名;
// 作用: 该类所有成员函数都能访问本类的
// 私有成员.
friend class Class2;
};
int MyClass::m_staticNum;
class Class2{
public:
void fun(MyClass& obj){
// 声明友元类之后, 就能在成员函数中
// 去访问MyClass类的私有变量
obj.m_nNum = 0;
}
};
- 友元成员函数 : 将本类的访问权限全部授予给另一个类某个成员函数(只有被授权的成员函数能够访问所有成员,没有被授权的成员函数访问不了)
- 互相引用的问题 : 在A类中使用了B类, B类又使用了A类. 类的声明就无法正常声明了. ```c class MyClass2 { public: void fun1(); void fun2(); };
class MyClass1 { int m_nNum; public: // 声明友元成员函数 friend void MyClass2::fun1(); };
void MyClass2::fun1() { MyClass1 obj; // 访问私有成员变量 obj.m_nNum = 0; } void MyClass2::fun2() { MyClass1 obj; // 访问私有成员变量, 但fun2并没有被声明成友元成员函数. 因此访问失败. obj.m_nNum = 0; }
1. 运算符重载有关
<a name="9bace9a3"></a>
# 运算符重载
1. 在C++中有很多会被自动调用的代码
1. 构造一个对象, 构造函数被调用了
1. 当一个对象被销毁的时候, 析构函数被调用
1. 当编译器需要进行隐式转换时, 转换构造被调用了.
1. 当一个对象被使用了运算符(+,-,*,/,%....)的时候
2. 无论是在C还是在c++中, 能够直接使用运算符进行运算的数据类型一般只有基本数据类型.
1. 意思就是, 无法直接将一个数组使用`+`,`-`,`*`等运算符, 结构体变量, 类对象同样如此.
1. 如果想要将非基本数据类型的变量使用运算符,一般需要通过另一些运算符来得到基本数据类型,然后再直接使用运算符.
1. 例如一个结构体变量,不能直接使用 +运算符, 但是通过`.`或`->`可以从结构体变量中得到一个字段(基本数据类型), 这个基本数据类型就能使用运算符了
3. 在C++中. 提供一种特殊的成员函数. 这些成员函数的名字都有一个相同的前缀,都叫`operator`, 他们一般都有一个不同的后缀, 不同的后缀就是运算符. 例如:`operator+` , `operator-`, 这些函数在使用的时候, 一般有两种形式:
1. `对象.operator+(5);`
1. `对象 + 5`
```c
string strobj2;
strobj2.operator+=("123");
strobj2 += "123";
- 运算符重载的意义:
- 为了能够让对象直接使用运算符.
- 运算符重载的本质:
- 一个运算符就是一个函数调用. 如果运算符是一个单目运算符,这个函数调用就不需要传递参数. 如果运算符是双目运算符, 那么运算符的左操作数就是对象自身, 右操作数就是函数中的形参1.
- 当对一个类对象使用运算符的时候, c++编译器在类中查找一个函数(以
operator
开头的函数),如果没有,就报错了. 如果找到了就调用该函数.- 调用函数的传参办法:
- 单目运算符 : 不用传递. 对象自身就是单目运算符的操作数, 例如:
++obj
; - 双目运算符:
- 成员函数版本: 对象默认作为左操作数, 右操作数作为参数1. 例如, obj + 5 会被转化成:
obj.operator+(5);
而5+obj就无法转化. - 友元函数版本: 根据左操作数作为形参1,右操作数作为形参2 , 例如: obj+5 会被转化成:
operator(obj,5)
, 5 + obj 会被转化成:operator(5,obj)
- 成员函数版本: 对象默认作为左操作数, 右操作数作为参数1. 例如, obj + 5 会被转化成:
- 单目运算符 : 不用传递. 对象自身就是单目运算符的操作数, 例如:
- 调用函数的传参办法:
运算符重载的使用
单目运算符的重载
class Numer {
int m_nNum = 0;
public:
// 前置++
Numer& operator++() {
++m_nNum;
return *this;
}
// 后置++
Numer operator++(int) {
// 先保存+1之前的值
Numer t;
t.m_nNum = this->m_nNum;
// 自身自增
++m_nNum;
// 返回自增前的值
return t;
}
// 前置--
void operator--() {
}
// 后置--
void operator--(int) {
}
};
int main()
{
Numer obj;
Numer obj2 = obj++;// 转换成运算符重载函数:obj.operator++();
obj.operator++();
// 如果是后置++, 编译器只会找operator++(int)
// 的成员函数 , 形参中的int只是为了区分前置和后置,
// 并没有传递实际的参数.
obj++;// 转换成运算符重载函数:obj.operator++(int);
--obj;// 转换成运算符重载函数:obj.operator--();
obj--;// 转换成运算符重载函数:obj.operator--(int);
++++++obj; //转换成:obj.operator++().operator++().operator++();
}
双目运算符的重载
成员函数版本
class Numer{
int m_nNum;
public:
Numer():m_nNum(){}
// 转换构造函数
Numer(int n) :m_nNum(n) {}
Numer(const char* pStr) {
sscanf_s(pStr, "%d", &m_nNum);
}
Numer operator+(const Numer& n) {
// 将自身拷贝到t
Numer t(*this);
t.m_nNum = this->m_nNum + n.m_nNum;
return t;
}
};
友元函数版本
对于成员函数版本的运算符重载有一个缺陷: 运算符的左操作数必须是对象才能完成调用, 对于左操作数不是对象的情况,就无法调用了,例如:obj3 = 123 + obj3 ;
会报错.
要解决这个问题, 就需要改变左操作数只能是对象
的现状
改变的方法就是使用友元版本的运算符重载.
友元版本的运算符重载在定义运算符重载函数的时候, 形参中必须指定两个操作数. 参数1默认是左操作数, 参数2默认是右操作数.(单目运算符默认不使用友元版本) ```c class Numer { int m_nNum = 0; public:// 默认构造 Numer():m_nNum(){}
// 转换构造函数 Numer(int n) :m_nNum(n) {} Numer(const char* pStr) {
sscanf_s(pStr, "%d", &m_nNum);
}
// 成员函数版本 // 只能在形参中指定右操作数 Numer operator+(const Numer& n) {
// 将自身拷贝到t
Numer t(*this);
t.m_nNum = this->m_nNum + n.m_nNum;
return t;
}
// 友元函数版本 // 必须在形参中指定运算符的两个操作数 friend Numer operator+(const Numer& left,
const Numer& right)
{
Numer t(left);
t.m_nNum += right.m_nNum;
return t;
} };
int main() { Numer obj3;
obj3 = obj3 + "123456";
// obj3.operator+(Numer("123456"));
// obj3 += "123456";
obj3 = 123 + obj3;//转换成: operator+(Number(123) , obj3);
}
<a name="8f3d2838"></a>
#### 对cout和cin的支持
```c
class Numer {
int m_nNum = 0;
public:
// 默认构造
Numer():m_nNum(){}
// 转换构造函数
Numer(int n) :m_nNum(n) {}
Numer(const char* pStr) {
sscanf_s(pStr, "%d", &m_nNum);
}
friend ostream& operator<<(ostream& o,
const Numer& obj)
{
o << obj.m_nNum;
return o;
}
friend istream& operator>>(istream& i,
const Numer& obj)
{
i >> obj.m_nNum;
return i;
}
};
int main()
{
Numer obj3;
obj3 = obj3 + "123456";
// obj3.operator+(Numer("123456"));
// obj3 += "123456";
obj3 = 123 + obj3;//operator(123 , obj3);
// 对cout和cin的支持
cout << obj3 << " sdfsadkf" << endl;
//可以转化成 : cout.operator<<(obj3);// 成员函数版本
//也可以转化成 : operator(count, obj3);// 友元函数版本
cin >> obj3;// operator(cin,obj3);
}