C++面向对象
- 面向对象概念
- 类与对象的区别
- C++中类的设计
- 设计实例
- 实例解释
- 共有和私有
- 类的认识
- 函数定义
- 函数在类里定义和类外定义区别
- 函数定义实例
- C++对象模型
- 方案一:各对象完全独立地安排内存的方案
- 方案二:各对象的代码区共用的方案:
- this指针
- this指针特点
- 程序编译面向对象程序的过程
- 修改具体过程
- 前后代码对比
- 修改前
- 修改后
- 全局函数和成员函数的区别
- 构造函数
- 构造函数的由来
- 构造函数用途--创建对象
- 构造函数用途--初始化对象中的属性
- 构造函数二义性
- 数据成员的构建顺序
- 类型转换
- 关键字explicit
- 单参的函数才具有类型转换
- Int b()问题
- 构造函数特征
- 析构函数
- 析构函数的定义
- 析构函数特点
- 全局变量和局部变量
- 常方法与对象的匹配关系
- 拷贝构造函数
- 友元函数
- 静态关键字:
- 对象与对象的关系
- 依赖
- 关联
- 聚合
- 组合
面向对象概念
对象的概念是面向对象技术的核心所在。面向对象技术中的对象就是现实世界中,某个具体的物理实体在计算机世界(逻辑)中的映射和体现。也就说计算机中的对象,是模拟现实世界中的实体。
类与对象的区别
类是设计的产物,设计是概念的产物 ,类不占有内存空间
对象是类的实例化,占有内存空间
C++中类的设计
设计实例
class CGoods{
private:char Name[21];int Amount;float Price;float Total_value;
public:void RegisterGoods(char ,int,f1oat);//输入数据void countTota1(void);//计算商品总价值void GetName(char[]);//读取商品名int GetAmount(void);//读取商品数量float GetPrice(void);//读取商品单价float GetTotal_value(void);//读取商品总价值
};
实例解释
共有和私有
四个数据成员被说明成私有,而六个函数成员被说明成公有;这就是说如果从外部对四个数据成员进行操作的话,只能通过六个公有函数来完成,数据受到了良好的保护,不易受外部环境的影响。
类的认识
类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。
成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。
函数定义
函数在类里定义和类外定义区别
在类里面声明,在类里面定义:系统默认该函数是inline函数,所以按照inline方式进行编译
在类里面声明,在类外面定义:该函数不是inline函数 ,如果想让其成为inline函数,就需要在类外定义该函数时,添加关键词inline。并且定义时,要在函数名前面加类型名::告诉编译器,该函数是该类的一个成员方法
函数定义实例
class CGoods{
private:char Name[21];int Amount;float Price;float Total_value;
public:void RegisterGoods(char ,int,f1oat);//输入数据void countTota1(void);//计算商品总价值{Tota1_value = Price Amount;}void GetName(char[]);//读取商品名int GetAmount(void);//读取商品数量float GetPrice(void);//读取商品单价float GetTotal_value(void);//读取商品总价值
};
void cGoods ::RegisterGoods(const char_*name,int amount,float price){strcpy(Name,name);//字符串拷贝函数Amount = amount;Price = price;
}
//void cGoods : :countTota1(void){
// Tota1_value = Price Amount;
//}
void cGoods: :GetName(char name[]){strcpy(name,Name);
}
int cGoods:: GetAmount(void) { return (Amount); }
float CGoods ::GetPrice(void) { return (Price);}
//float cGoods::GetTotal_value(void) { return(Total_value); }
对象是类的实例(instance)。声明一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板(图纸),以此样板可以在内存中开辟出同样结构的实例–对象。
C++对象模型
方案一:各对象完全独立地安排内存的方案
上图是系统为每一个对象分配了全套的内存,包括安放成员数据的数据区和安放成员函数的代码区。但是区别同一个类所实例化的对象,是由属性(数据成员)的值决定,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有的对象都是一样的。
方案二:各对象的代码区共用的方案:
上图仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。
this指针
this指针特点
this指针的特点是自身是一个常性指针,在类的成员函数都要添加this指针,只要是面向对象的语言,编译器都会进行一个修改
程序编译面向对象程序的过程
编译器针对程序员自己设计的类型分三次编译。
第一:识别和记录类体中属性的名称,类型和访问限定,与属性在类体中的位置无关。如class CGoods 中的Name,Amount,Price,Total_value;
第二:识别和记录类体中函数原型(返回类型+函数名+参数列表),形参的默认值,访问限定。不识别函数体。
第三:改写在类中定义函数的参数列表和函数体,改写对象调用成员函数的形式;
修改具体过程
给每一个成员函数加一个指向当前类型的this指针,该指针指向定义的对象的地址,该对象就是该类的一个实例化
函数里面只要是第一步进行类体属性识别过的属性,或者是类的方法成员,都要加this指针
形参不用加this指针
在调用函数(主函数)中,tea.RegisterGoods(“b1ack_tea”,12,560);实际上是把茶对象作为一个参数,调用该函数
//RegisterGoods(&tea,“black_tea”,12,560);
this指针指向的是tea的地址,然后填入后面的值
this指针是const指针,一旦调用了函数,指针自身不能修改,指向可以被修改
CGoods*const this
调用一次函数,this指向一个对象的地址,该函数调用完成,分配的栈帧释放,下次调用函数,this指针指向另一个对象的地址
前后代码对比
修改前
class CGoods{
private:char Name[21];int Amount;float price;float Tota1_va1ue;pub7ic:
public:void RegisterGoods(const char *name ,int,float);void countTota1(O);
};
void cGoods::RegisterGoods(const char *name ,int amount,float price){strcpy(Name,name);//字符串拷贝函数Amount = amount;Price = price;
}
void ccoods::countTota1(){Total_value = Price *Amount;
}
int main({cGoods tea;cGoods book;tea.RegisterGoods("b1ack_tea",12,560);tea.countTota1();book.RegisterGoods( "Thinking In C++",20,128);book.countTota1();return 0;
}
修改后
class CGoods{
private:char Name[21];int Amount;float price;float Tota1_va1ue;pub7ic:
public://void RegisterGoods(CGoods*const this,const char *name ,int,float);void RegisterGoods(const char *name ,int,float);//void countTota1(CGoods*const this);void countTota1();
};
void cGoods::RegisterGoods(CGoods*const this,const char *name ,int amount,float price){strcpy(this->Name,name);//字符串拷贝函数this->Amount = amount;this->Price = price;
}
void ccoods::countTota1(CGoods*const this){this->Total_value = this->Price *this->Amount;
}
int main({cGoods tea;cGoods book;tea.RegisterGoods("b1ack_tea",12,560);//RegisterGoods(&tea,"black_tea",12,560);tea.countTota1();//countTota1(&tea);book.RegisterGoods( "Thinking In C++",20,128);//RegisterGoods( &book,"Thinking In C++",20,128);book.countTota1();//countTota1(&book);return 0;
}
全局函数和成员函数的区别
重点是this指针,其间的差别在于一个是程序的需要,一个是编译器的需要。
构造函数
C语言特点有空间即可操作
面向对象的特点有空间不一定有对象,有对象一定有空间
构造函数的由来
数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数。
构造函数用途–创建对象
这里其实是调用了无参的构造函数,只是将()省略了,Complex c1;如果不省略,会和函数声明发生二义性
Complex():real(0),image(0) {//real = 0; image = 0;}
初始化列表是在该空间构建整型数据,而一般的赋值是在构建之后,赋值而已
构造函数用途–初始化对象中的属性
#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:int real; int image;
public:Complex():real(0),image(0) {//real = 0; image = 0;}Complex(int r, int i) {real = r;image = i;}
};
int main() {Complex c1;Complex c2(1,2); return 0;
}
构造函数二义性
这里定义c1会产生二义性,可以调用无参的构造函数,也可以调用有参的构造函数,
只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。
只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,但是规定缺省的构造函数只能有一个。而这里有两个,所以编译不通过
#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:int real; int image;
public:Complex():real(0),image(0) {//real = 0; image = 0;}Complex(int r=0, int i=0):real(r),image(i) {//real = r;//image = i;}
};
int main() {Complex c1;//error ,直接省略()Complex c2(1,2); return 0;
}
数据成员的构建顺序
初始化列表的构建顺序,并不是数据成员的构建顺序,数据成员是按照类设计的顺序构建(是对象在内存中的顺序),所以需要先构造实部,real(image),此时image里面是随机值,所以real是随机值,image是10
#include<iostream>
#include<vector>
using namespace std;
class Complex {
private:int real; int image;
public:Complex(int x=0):image(x),real(image) {}Complex(int r, int i):real(r),image(i) {}void Print() { cout << "Real:" << real << "--" << "Image:" << image << endl; }
};
int main() {Complex c1(10);c1.Print();//随机值 10return 0;
}
类型转换
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
};
int main() {Int a(10);Int b{ 20 };Int c = Int(30);int x = 200;a = x;return 0;
}
定义一个int x值为200,然后将x的值给Int(int x=0)里面的x,创建一个不具名对象,由构造函数实现
实际上他的写法为
a=(Int)x;
将x强转为Int,和C语言强转方式一样
关键字explicit
有一个关键字explicit,该关键字叫明确关键字,如果用该关键字在构造函数前面 ,则不能用该方式构建,即赋值方式
Int e = 100;//error
加了该关键字,就失去了隐式转换能力,必须强转
a = x;//errora = (Int)x;
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }
}; int main() {Int a(10);Int b{ 20 };Int c = Int(30);Int d = Int{40};Int e = 100;int x = 200;a = x;a = (Int)x;return 0;
}
单参的函数才具有类型转换
这里无法进行类型强转
class Int {
private:int value;
public:Int(int x ,int y) :value(x+y) { cout << "create Int" << this << endl; }
}; int main() {Int a(1, 2);a=(100,200);//errorreturn 0;
}
通过缺省函数,使其成为单参
class Int {
private:int value;
public:Int(int x ,int y=0) :value(x+y) { cout << "create Int" << this << endl; }
}; int main() {Int a(1, 2);a=(100,200);return 0;
}
class Int {
private:int value;
public:Int(int x ,int y=0) :value(x+y) { cout << "create Int" << this << endl; }void Print()const { cout << value << endl; }
}; int main() {Int a(1, 2);a = (Int)(100, 200);a.Print();//200a = Int(100, 200);a.Print();//300return 0;
}
a = (Int)(100, 200);这里他认为是要调用单参构造函数,逗号表达式取最右边的200,赋值给x,因为单参的构造函数才能进行强转的转换
a = Int(100, 200);直接调用
Int b()问题
如果给括号,一定给实参,没有给实参,一定将该圆括号删掉,或者给个{},因为Int b()这样,编译器并不认为这是构造b对象,而是认为了函数的声明,是因为C++兼容C语言,在C++11 初始化用{ }
class Int {
private:int value;
public:Int(int x=0) :value(x) { cout << "create Int" << this << endl; }void Print()const { cout << value << endl; }
};
int main() {Int a;Int b();//这个认为了函数的声明Int c{};return 0;
}
构造函数特征
构造函数是特殊的公有成员函数(在特殊用途中构造函数的访问限定可以定义成私有或保护),其特征如下:
1.函数名与类名相同。
⒉.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上构造函数有返回值,返回的就是构造函数所创建的对象。
3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也只调用这一次。
4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。
5.构造函数可以在类中定义,也可以在类中声明,在类外定义。但是默认值必须在声明时给,不能在定义时给,否则会出现二义性
6.如果类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数;类名(void){ }
构造函数也有this指针,改写,在编译的时候就可以确定类型的大小,
在调试的时候,没有赋值之前,全是随机值,空间有,没有对象
析构函数
析构函数的定义
当定义一个对象时,C++自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数即析构函数(destructor):
析构函数特点
1.构函数名与类名相同,但在前面加上字符~,如: ~CGoods () 。
⒉析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。
3.一个类有一个也只有一个析构函数,这与构造函数不同。
4.对象不能调用构造函数,但是,可以调用析构函数,
6对象注销时(主函数结束,释放栈帧),系统自动调用析构函数。
5.如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。
全局变量和局部变量
静态量在数据区,不能重复建立
代码编译的时候关注的是可见性,代码运行起来关注生存期
进入主函数前,要创建全局变量
class object{
int value; public:
object(int x = 0) : value(x) {cout << "create object value : " << value << endl;
}
~object() {cout << "Destroy object value: " << value << endl;}
};
object obja(1);
int main()
{object objb(2);//objectc.show();//加了这个程序直接无法编译通过return 0;
}
object objc(3);
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }~Int(){cout << "Destroy Int:" << this << endl;}//void func(Int *const this)void func() { cout << "Int func" << endl; }//void func(const Int *const this)void Print()const { cout <<"Int::value:" << value << endl; }
};
int main()
{Int* ip = nullptr;ip->func();//程序执行结果 Int funcip->print();//error ip为空,空不能解引用return 0;
}
//这两等价
ip->func();func(ip);
ip本来就指向对象的地址,ip->func() 就是(ip).func();就是func(ip);
Int a;
a.func();//实际是func(Intconst this);该指针是指向a对象的地址,
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }~Int(){cout << "Destroy Int:" << this << endl;}void func() { cout << "Int func" << endl; }void Print()const { cout <<"Int::value:" << value << endl; }
};
int main()
{Int* ip = (Int*)malloc(sizeof(Int));if (nullptr == ip)exit(EXIT_FAILURE);ip->func();ip->Print();return 0;
}
打印随机值,有空间没有对象
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }~Int(){cout << "Destroy Int:" << this << endl;}void func() { cout << "Int func" << endl; }void Print()const { cout <<"Int::value:" << this->value << endl; }
};
int main()
{Int* ip = new Int(10);ip->Print();delete ip;ip = nullptr;return 0;
}
这里new的动作
1.sizeof(Int);
2.malloc();
3.构造对象;
4.return;
delete的动作
1.~Int()释放对象资源
2.free()将ip指向的空间还给系统
new一组对象,用delete[]删除一组对象
如果用delete 只能调用一次析构函数
开辟10个整型,在上下越界标记之间,用一个int标记创建对象个数,所以析构时,根据标记的值,依次析构
当我们设计类型时,这个类型有析构函数的时候,就会用4字节存放创建对象个数,当我们设计类型时,这个类型没有析构函数的时候,就不会在删除的时候调用析构函数,就不需要有这个计数值,就没有这个多出来的4字节
class Int {
private:int value;
public:Int(int x = 0) :value(x) { cout << "create Int" << this << endl; }~Int(){cout << "Destroy Int:" << this << endl;}void func() { cout << "Int func" << endl; }void Print()const { cout <<"Int::value:" << this->value << endl; }
};
int main()
{int n = 0;Int* ip = nullptr;cin >> n;ip = new Int[n];Int* s = new Int(n);for (int i = 0; i < n; i++) {ip[i].Print();}delete[] ip;ip = nullptr;delete s;s = nullptr;return 0;
}
类型Int中有析构函数时,在堆区开辟内存中会用4个字节空间存放创建的对象个数,用于调用析构函数时,删除该数量的空间
常方法与对象的匹配关系
在函数后加const,修饰函数自身
当对象调用常方法时,只能读取对象的属性的值,只有只读权限,不能对对象属性修改,而可以修改非对象成员
//void Print(const Int*cosnt this)
void Print()const
普通对象优先调用普通方法,如果普通方法不存在,退而求其次,调用常方法,如果是常对象,只能调用常方法
普通对象优先结合普通引用,如果普通引用不存在,退而求其次,结合常引用,如果是常对象,只能结合常引用
普通方法可以调用常方法,常方法不能调用普通方法
void func(){cout<<"func"<<value<<endl;}
void func() const {cout<<"func const"<<value<<endl;}
class Int
{
private:int value;
public:Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }Int(const Int& it) :value(it.value) {}~Int() { cout << "Destroy Int: " << this << endl; }void Print() { cout << value << endl; }Int Add(Int b) {Int c;c.value = this->value + b.value;return c;}void SetValue(int x) { value = x; }int GetValue()const { return value; }};int main()
{Int a(10);int x = a.GetValue();a.SetValue(200);return 0;
}
上面的函数可以改成如下的样式,用一个函数实现了取值和设置值的作用,该Value函数返回的是引用,即别名
一个函数以引用的形式返回,他的特点是此数据的生存期不受此函数的影响,函数死了,该数据还活着
int&fun(){int x=10;return x;
}
这种返回是可以的,这种返回就是把一个死亡对象返回,函数死了,以引用返回的量不死亡
int&Value(){return value};
cosnt int&Value()const{return value};
用一个函数实现取值和修改值,用一个常方法再定义一遍,即可以对付普通对象,也可以对付常对象,使程序的健壮性更好
class Int
{
private:int value;
public:Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }Int(const Int& it) :value(it.value) {}~Int() { cout << "Destroy Int: " << this << endl; }void Print() { cout << value << endl; }Int Add(Int b) {Int c;c.value = this->value + b.value;return c;}int& Value() { return value; }
};int main()
{Int a(10);int x = a.Value();a.Value() = 200;return 0;
}
class Int
{
private:int value;
public:Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }Int(const Int& it) :value(it.value) { cout << "Copy Create Int" << this << endl; }~Int() { cout << "Destroy Int: " << this << endl; }void Print() { cout << value << endl; }int& Value() { return value; }
};
void funa(Int x) {x.Value() = 100;
}
void funb(Int& y) {}
void func(const Int&z){}int main() {Int a(10);funa(a);funb(a);func(a);
}
void funa(Int x)这种情况x是a的一份拷贝,其中调用拷贝构造函数,建立新的对象x,x的改变不影响a,不用用这种方式,效率问题,如果按照值的形式,如果效率会大大下降。传引用的话,引用在底层是一个指针,void funb(Intconst y)所以传引用比传值更快
void funb(Int& y) ,void funb(Intconst y)这种情况是y是a的一个别名,y的改变会影响a
void func(const Int&z) 这里只能读取,因为是常引用
如果你只想读取数据,不改变形参,就加上const,用常引用,如果想通过更改形参更改数据,就可以去掉const,用普通引用
Int func(int x) {Int tmp(x);return tmp;
}
Int fund(int x) {return Int(x);
}
int main() {Int a = func(100);Int b = fund(200);cout << a.Value() << endl;
}
Int func(int x)这里一共创建了两个对象,程序会对其有一个优化,将亡值对象不构建,直接构建a对象,所以最终,一个tmp对象,一个a对象
Int fund(int x) 这里一共创建了一个对象,就是b对象本身,也有优化
为了减少对象的创建一个,一般用Int fund(int x)这种方式
Int& func(int x) {Int tmp(x);return tmp;
}
int main() {Int a(10),b(20);a = func(100);cout << a.Value() << endl;
}
func栈释放后,以引用形式返回,tmp对象死亡了,但是残留在内存的数据还是在那里,从已死亡的对象取值,系统默认的赋值语句是几个汇编语句完成的,原来的func函数开辟的空间不会收到影响,仍然可以获取100,
如果我们在类里自己定义一个赋值语句,就会再次利用func的栈帧,就会填充,就会覆盖tmp的值。
所以不能以Int& 返回,从已死亡的对象抽数据
如果是静态区,对象不死亡,就可以捞数据,当第二次调用的时候,tmp就不会构建,只会构建一次
Int& func(int x) {static Int tmp(x);return tmp;
}
可以以引用的形式返回对象的情况
此对象的生存期不收该函数的影响,当函数调用结束后,此对象还活着,这种对象就是静态对象或者是全局对象,
拷贝构造函数
当拿一个对象初始化另一个对象的时候,调用拷贝构造函数
当函数调用时,形参和实参结合时,调用拷贝构造函数,
以值返回一个对象,需要建立一个将亡值对象,此时也要调用拷贝构造函数
Complex(const Complex& cx) :Real{ cx.Real }, Image{ cx.Image } {cout << "Copy Object:" << this << endl;;
}
这里Complexc2(c1)调用了拷贝构造函数,c3=func(c1),参数传递时,调用了拷贝构造函数,该函数返回的时候,以值的形式返回的时候,产生了一个将亡值,将结果赋值给将亡值,调用了拷贝构造函数
class Int
{
private:int value;
public:Int(int x = 0) :value(x) { cout << "Create Int: " << this << endl; }Int(const Int& it) :value(it.value) {}~Int() { cout << "Destroy Int: " << this << endl; }void Print() { cout << value << endl; }//Int Add(const Int*const this,Int x)Int Add(const Int& x) const {return Int(this->value + x.value);}
};
int main() {Int a(10), b(20), c;a.Print();b.Print();c.Print();c = a.Add(b);c.Print();
}
Int Add(const Int& x) const {Int tmp;tmp.value=this->value+x.value;return tmp;//构建将亡值对象,
}
这里创建了两个对象,tmp和将亡值对象
Int Add(const Int& x) const {return Int(this->value + x.value);
}
这里创建了一个不具名对象(类型加括号),调用构造函数创建了一个对象
友元函数
友元friend机制允许一个类授权其他的函数访问它的非公有成员.
友元声明以关键字friend开头,它只能出现在类的声明中,它们不受其在类体中的public private和protected区的影响.
友元分为外部函数友元,成员函数友元,类友元。
友元函数可以访问这个类产生的对象的私有成员
1.不具有对称性︰A是B的友元,并不意味着B是A的友元。
2.不具有传递性︰A是B的友元,B是C的友元,但A不是C的友元。
3.不具有继承性: Base类型继承Object类型,如果Object类型是A的友元,但Base类型不是A友
class Int {
private:int value;
public:Int(int x = 0) :value{ x } { }~Int(){}friend void Print(const Int& it);
};
void Print(const Int& it) {//value = 20;//errorcout << it.value << endl;
}
int main()
{Int a{ 10 };Print(a);//cout << a.value;//errorreturn 0;
}
class Int;
class Object {
public:void Print(const Int& it);
};
class Int {
private:int value;
public:Int(int x = 0) :value{ x } { }~Int() {}friend void Object::Print(const Int& it);
};
void Object::Print(const Int& it) {cout << it.value << endl;
}
int main() {Object obj;Int a(10);obj.Print(a);return 0;
}
注意次序
class Int;
class Object {
public:void Print(const Int& it);
};
class Int {friend class Object;
private:int value;
public:Int(int x = 0) :value{ x } { }~Int() {}
};
void Object::Print(const Int& it) {cout << it.value << endl;
}
int main() {Object obj;Int a(10);obj.Print(a);return 0;
}
静态关键字:
在c语言中用静态关键字修饰局部变量,
static int a = x;只在数据区定义一次,不会重复定义,即该代码只会被执行一次,
对于全局变量,加静态关键字,static int g_max=10;该变量只在本文件有效,在同一个工程下其他文件不可见
如果希望次函数只在本文件可见,在同一个工程下其他文件不可见,也可以直接加static
void fun(int x) {static int a = x;int b = 0;a += 1;b += 1;printf("a=%d,b=%d\n", a, b);
}
int main() {for (int i = 5; i > 0; i--) {fun(i);}return 0;
}
不要把形参修饰成静态量,编译器将忽略静态量修饰形参,仍然在栈上开辟
编译链接只对.cpp或者.c,不对.h编译链接
在main.cpp中,如果想用int g_max = 10;就必须用extern关键字,说明该变量是同一个工程下的变量,使该处可见
extern int g_max;
int main() {g_max += 10;func();
}
在func.cpp中,如果你不想让g_max被引用,就可以加static修饰,就只能在本文件可见,及时在main.cpp中加extern关键字也不行
int g_max = 10;
void func(){}
如果加const
const int g_max=0;
--------------------
extern const int g_max;
int main() {g_max += 10;func();
}
如果加const,这种情况是错误的,会产生二义性
const int g_max=0;
--------------------
extern const int g_max=10;
int main() {g_max += 10;func();
}
如果常变量在其他文件可以用,就加一个extern关键字,在其他文件用的时候加extern关键字
extern关键字用法总结:
静态数据成员
静态的目的是为所有的对象共享一个数据成员
在类里面用static申明,他就不是类所产生的对象的数据成员,它时这个类产生所有对象所共享的数据,申明并不意味着开辟空间,申明的静态成员在类外进行定义,开辟空间,并且初始化
在构造函数中不能拿参数列表对pi进行初始化,但是可以在构造函数赋值,因为所创建的静态量是所有对象共享的,它只有一份,他在类外已经被被构建了,不能再次构建,初始化列表就是在构建
using namespace std;
class Circle {
private:static double pi;//申明 .datadouble radius;
public:Circle(double r=0.0):radius(r){}~Circle(){}double area()const {return pi * radius * radius;}void showPI()const {cout << pi << endl;}void SetPI(double x) {pi = x;}
};
double Circle::pi = 3.14;//定义,INIT
int main() {Circle c1(10), c2(20), c3(30);cout << sizeof(c1) << endl;c1.showPI();c2.SetPI(3.1415926);c1.showPI();return 0;
}
有一个特殊性,只有char short int 可以
如果定义一个静态常量,就可以在类里面定义加赋值
class Object {
public:static const int num = 0;
};
或者这样,注意赋值的时候不要加static
class Object {
public:static const int num;
};
const int Object:: num = 0;
//void func(const Circle *const this)void func()const {//this->radius += 10;//errorpi += 10;}
在常方法里对非静态成员无法修改,因为用const修饰了this指针的指向,而静态成员可以修改,因为静态成员不在对象的空间中,在.data中 ,
在C语言中,有三个域,全局域,局部域,块域,而C++又新增加了一个类域
如果想要访问共有的静态成员的时,需要这样,就告诉编译器 ,pi属于这个类
Circle::pi = 10;
不能成为常方法的函数有全局函数,全局函数没有this指针,还有静态函数,
静态的函数没有this指针,所以就不能将静态函数定义为常方法
外部函数不能访问对象的私有成员,但是静态函数可以访问obj的私有成员,
using namespace std;
class Object {
private:static int num;int value;
public:Object(int x=0):value(x){}void print()const {cout << "num:" << num << "value" << value << endl;}static void show(Object& obj) {//not thiscout << "num" << endl;cout << "value:" << obj.num << endl;}
};
int Object::num = 10;
int main() {Object obja(10), objb(20);obja.show(obja);obja.show(objb);Object::show(obja);
}
对象与对象的关系
依赖
#include<thread>
using namespace std;
class Book{};
class Food{};
class Human {
public:void Read(Book* pbook);void Eat(Food* pfood);
};
int main() {Book book;Food food;Human man;man.Read(&book);man.Eat(&food);
}
关联
class Teacher {
private:Course&cs;
public://Teacher(){}Teacher(Course c):cs(c){}//error
};
int main() {Course ca, cb, cc;Teacher tyh(ca);return 0;
}
cs引用一个形参对象的别名,当函数销毁,形参也会销毁,cs会引用一个已经死亡的对象
class Teacher {
private:Course&cs;
public://Teacher(){}Teacher(Course&c):cs(c){}//error
};
int main() {Course ca, cb, cc;Teacher tyh(ca);return 0;
}
加一个引用,c是ca的别名,再用c初始化cs,就是,cs是ca的别名
聚合
不存在生命的依赖
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
#include<iostream>
#include<thread>
using namespace std;
class Point {
private:float _x;float _y;
public:Point(float x=0,float y=0):_x(x),_y(y){}Point(const Point&) = default;Point& operator=(const Point&) = default;~Point(){}};
class Circle {
private:Point _center;float _radius;static const float pi;
public:Circle(){}
};
const float Circle:: pi = 3.14;
int main() {Circle cir;Point p(1, 2);return 0;
}
组合
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
#include<iostream>
#include<thread>
using namespace std;
class Soul {};
class Body{};
class People {
private:Soul _soul;Body _body;
public:People(){}
};
int main() {People pe;return 0;
}
class Int {
private:int value;
public:Int(int x = 0) :value(x) {cout << "Create Int" << this << endl;}Int(const Int& it) :value(it.value) {cout << "Copy Create Int" << endl;}Int& operator=(const Int& it) {if (this != &it) {this->value = it.value;}cout << "this" << "operator=() " << &it << endl;return *this;}~Int() {cout << "Destory Int:" << this << endl;}int& Value() {return value;}const int& Value()const {return value;}};
class Object {Int num;
public:Object() { cout << "Create Object" << this << endl; }~Object() { cout << "Destroy Object;" << this << endl; }
};
int main() {Object obj;return 0;
}
先构建类的成员对象
Object() { cout << “Create Object” << this << endl; }
这里他会先调用Int的缺省构造函数,构建Int,再构建Object,如果没有Int的缺省构造函数,就会报错
有两个改动点可以解决
Object(int x=0):num(x) { cout << “Create Object” << this << endl; }
或者
在Int类里定义缺省构造函数
Int():value(0){}
析构和创建相反,先析构Ojbect再析构Int
成员对象Int有拷贝构造,Object没有拷贝构造,当Object objc(obja);时,调用Int的拷贝构造
成员对象Int没有拷贝构造,Object有拷贝构造,当Object objc(obja);时,调用Int的缺省构造函数,而无法调用成员对象的拷贝构造函数
成员对象Int有拷贝构造,Object有拷贝构造,当Object objc(obja); 时,调用Int的缺省构造函数,只有在Object中拷贝构造函数中给出明确的构造者
Object(const Object& obj) :num(obj.num) {cout << "Copy Create Object" << this << endl;
}
成员对象Int没有拷贝构造,Object没有拷贝构造,当Object objc(obja); 时,系统会合成缺省的拷贝构造,能实现成员对象的赋值
using namespace std;
class MyString {
private:struct StrNode {int ref;//引用计数int capa;int len;char data[0];};StrNode* pstr;StrNode* GetNode(int total) {StrNode* s = (StrNode*)malloc(sizeof(StrNode) + sizeof(char) * total);if (nullptr == s)exit(EXIT_FAILURE);return s;}void FreeNode(StrNode*p) {free(p);}
public:MyString(const char* p = nullptr) :pstr(nullptr) {if (p != nullptr) {int len = strlen(p);int total = len * 2;pstr = GetNode(total);pstr->ref = 1;pstr->len = len;pstr->capa = total - 1;strcpy(pstr->data, p);}cout << "Create MyString " << this << endl;}MyString(const MyString& s) :pstr(s.pstr) { if (pstr != nullptr) {pstr->ref += 1;}cout << "Copy Create MyString " << this << endl;}MyString& operator=(const MyString& s) {if (this == &s || this->pstr == s.pstr){return *this;}if (pstr != nullptr && --pstr->ref == 0) {FreeNode(pstr);}pstr = s.pstr;if (pstr != nullptr) {pstr->ref += 1;}cout << "operator= " << this << endl;return *this;}void PrintString()const {if (pstr != nullptr) {cout << pstr->data << endl;}}MyString(MyString&& s) :pstr(s.pstr) {s.pstr = nullptr;}MyString& operator=(MyString&& s) {if (this == &s || this->pstr == nullptr && this->pstr == s.pstr) {return *this;}if (pstr != nullptr && --pstr->ref == 0) {FreeNode(pstr);}pstr = s.pstr;s.pstr = nullptr;return *this;}~MyString() {if (pstr != nullptr && --pstr->ref == 0) {free(pstr);}pstr = nullptr;cout << "Destory MyString " << this << endl;}
};
int main() {MyString s1("yhping");MyString s2(s1);s2.PrintString();return 0;
}