文章目录
一 访问权限
C++类的成员有访问限制,对于一般的成员变量和成员函数,有public, protected, private,即公有,保护,私有三种;一方面用于决定一个成员在类外是否能被访问,另一方面决定了子类继承获得父类中的一个成员在子类中的权限;
1、什么是访问限制,什么是访问?
访问就是能不能用,能不能改;对于变量,就是能不能查看值和修改值,对于函数,就是能不能调用,函数能不能访问变量,就是函数内能不能查看和修改变量的值;
访问限制,一般地,类中有成员变量和成员函数,在类中的成员函数可以访问类中的成员变量,而在类外,即在定义类,定义类对象之后的操作中,能否在全局中直接访问一个对象中的成员变量或者成员函数;
(1)在public:后,另一个访问限制符之前,所包含的成员为公有成员,是类对外的接口,公有成员函数可以在类外被调用,公有成员变量既可以被类中函数访问,也可以在类外被直接访问;
注意:
(1)在类中,可以任意数量和任意顺序写访问限制符,只有一点注意:类中默认是private:,所以如果最前面不写其他访问限制符,所定义的成员都是private的,不过借这点可以偷懒一下,凡是私有成员,都往最前面写就可;
(2)构造函数和析构函数必须是public,它们都是在类外被调用的;定义为保护和私有符合语法但没有用是没有意义的;
#include <iostream>
using namespace std;
class a
{
int var_a;
public:
a();
~a();
int pub_b;
void pub_a();
protected:
int prot_c;
void prot_d();
private:
int priv_e;
void priv_f();
};
int main()
{
a c;
c.var_a=0;//错误
c.pub_b=1;//正确
c::pub_a();//正确
c.prot_c=2;//错误
c::prot_d();//错误
c.priv_e=3;//错误
c::priv_f();//错误
return 0;
}
上面的代码说明只有公有成员才能在类外被访问;
2、子类继承的成员权限
基类权限 | public继承 | protected继承 | private继承 |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
二 this指针
在一个类中有成员变量和成员函数,在成员函数中,因为无法在定义类的时候事先知道将来实例化出来的对象的对象名,也就无法对将来的对象操作,也就是说,用类定义一个对象,在这个对象的成员函数中无法操作对象本身。例如
类成员初始化问题:
先看下面的代码
using namespace std;
class a
{
public:
int b;
a(int b)
{
cout<<(b=b)<<endl;//(b=b)就是b的值,观察结果这里的是哪个b
cout << "create" << endl;
}
~a()
{
cout << b << endl;//这里不存在二义性,只有类成员b;
cout<<"destroy" << endl;
}
};
int main()
{
a c(2);
return 0;
}
观察结果可知,对象c中的成员b并没有被初始化,而构造函数中的形参b确实被用到了,这是怎么回事呢?
2//形参b
create
4200523//成员b未被初始化
destroy
在构造函数中,根据语法,优先选择局部变量,因此,b=b两个b都是形参b,而析构函数中没有形参,才会选择对象的成员变量,结果就是成员变量b并没有被初始化,怎么办?
方案一:不在构造函数中初始化,而在主函数中访问;
class a
{
public:
int b;
a()
{
cout << "create" << endl;
}
~a()
{
cout << b << endl;//这里不存在二义性,只有类成员b;
cout<<"destroy" << endl;
}
};
int main()
{
a c;
c.b=2;//直接给它赋值;
return 0;
}
这样可以,但是不完善,如果定义b为保护成员或者私有成员,不能在外面访问了,又怎么办呢?
方案二:使用this指针
C++中有了this指针,在定义类时,编译器在每个非静态成员函数的第一个形参的位置前加上一个指针形参,当对象调用成员函数时,编译器将这个对象的地址作为实参传给这个形参,于是在调用的函数中就有了指向对象本身的指针,就能在成员函数内能访问这个对象;
也就是说:
1、定义类的时候并不知道对象;编译器会添加一个指针形参;定义对象后,在对象调用函数时将对象的地址传入;
2、构造函数中没有this指针,因为构造函数是定义对象的时候调用,那时还没有对象,也就没有对象的地址,所有仍然不能使用this指针;
3、也就是说,在构造函数之后的非静态成员函数都有了this指针,析构函数也有;
4、两个用途:一是用于访问对象本身,而间接访问对象的成员变量;这样就避免了使用参数二义性问题;二是在return 时,可以直接把对象本身返回,
class a
{
int b;
public:
a(int b)
{
this->b=b;//this指向的b是成员b,未指向的优先选取形参b;
cout<<this->b<<endl;
cout << "create" << endl;
}
~a()
{
cout << this->b << endl;
cout<<"destroy" << endl;
}
};
int main()
{
a c(2);
return 0;
}
2
create
2
destroy
可以看到,this指针完美得完成了任务;
三 构造函数
与类同名的成员函数,没有返回值;用于初始化类中成员;
1、特点
1 与类同名,无返回值;
2 如果没有显式定义构造函数,则系统会生成有一个默认构造函数 ,无参,函数为空,在定义对象时调用默认构造参数;
3 如果自定义了构造函数,将不会调用默认构造函数,而调用自定义的构造函数;
4 构造函数无需用户主动调用,在创建对象的时候,系统自动调用构造函数 ,而且只会被调用一次;
5 构造函数可以重载;
6 构造函数可以用初始化列表的方式对成员进行初始化,或者函数体赋值方式初始化;
7 构造函数如果定义成非公有(限制构造函数),则该类无法创建对象;
8 构造函数没有this指针,因为构造函数才是创建对象的,还没有创建对 象就不会有对象的地址;
2、分类
有:默认构造函数,无参构造函数,有参构造函数,限制构造函数,拷贝构造函数;
2.1有参构造函数
两种写法:
2.1.1、赋值的写法:
就和普通函数一样,由形参给成员变量赋值;
2.1.2、初始化列表:
类名(形参列表):成员1(形参1),成员2(形参2) { };
注意:
初始化顺序是类中定义成员的先后顺序,而不是初始化参数列表中的顺序;
#include <iostream>
using namespace std;
class a
{
public:
a(int b, int c) : var_a(b), d(c) {};
~a(){};
int var_a;
int d;
};
int main()
{
a c(1,2);
cout<<c.var_a<<c.d<<endl;
return 0;
}
12
2.1.3、必须用参数化列表构造函数的三种情况
(1)成员中有类对象,且其构造函数中只有有形参的,没有无参构造函数;
首先,对象构造函数有形参,初始化时必须传参,也就是必须在定义时就传参;
而对象作为成员,不能、不是在任何成员函数内定义,所以用构造函数进行初始化时不能放到函数体内;否则就:给一个未调用过构造函数的对象赋值,显然是错误的;
参数化列表早于构造函数体而且直接给对象的构造函数传参,这样就使对象的构造函数获得了参数得到了初始化;
(2)成员变量是const或者引用,
这两个都是必须在定义时就初始化的,和(1)中的情况一样;
(3)子类初始化父类的成员变量,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数
详细代码请翻参考部分大佬的文章;
应该尽量用初始化列表形式;
2.2、拷贝构造函数
当用一个对象做形参去初始化另一个对象,或者用对象赋值时,所执行的构造函数就叫拷贝构造函数;
拷贝构造函数也可以自定义的,第一个形参必须是对象的引用(一般只有一个形参),自定义后编译器不再生成默认的拷贝构造函数;
2.2.1、浅拷贝
如果不显式定义拷贝构造函数,那么编译器自动生成默认拷贝构造函数,内容就是把类成员变量的值一对一的复制粘贴;一般情况下就够用;
但在某些情况下就会有问题:如果成员中有指针,并且指向new出的空间,浅拷贝只会复制指针的值而不会再new内存空间,那么对象b和对象a中的指针指向同一块内存,当用对象b的指针去改值,相当于把对象a的值也给改了;就会混乱,所以遇到指针时要用到深拷贝;
2.2.2、深拷贝
深拷贝就是对于指针和new,在拷贝时也进行new内存空间,这样对象a的指针和对象b的指针指向不同的内存空间,也就不会互相干扰;
那么可以说,深拷贝构造函数都是需要程序员自定义的;
四 析构函数
作用:在对象的生命周期结束时,释放对象所占的内存资源
1、特点
1、函数名与类名相同,函数名前加~,没有返回值,没有参数的成员函数;
2、有默认的析构函数,没有自定义析构函数,就会调用默认的析构函数;有自定义的析构函数,系统将只会调用自定义的析构函数
3、不需要去主动调用析构函数,对象生命周期结束时,系统会自动调用
4、可以主动调用析构函数,但是此时只是执行了函数体,并不会释放对象内存
5、析构顺序和构造顺序相反
6、如果对象在堆中存放,delete时调用析构函数
7、如果在构造函数中或其他成员函数中使用了new申请了空间,就需要在析构函数中使用delete来释放空间
五 static静态成员
1、静态成员变量
1.1 怎么定义一个静态成员变量
定义一个静态成员变量分为两步,两步必须都完成才可以;
(1)在类中静态声明;
(2)在全局区初始化;
首先,作为类的成员,得在类中声明,
但是,不能在类中定义,静态变量定义时会分配内存,而定义类不分配内存,所以不能在类中定义;
其次,静态成员变量是整个类共有,不是任何函数的局部变量,所以不能在函数内部定义,指main()函数;
综上,静态成员变量必须在类中声明,在类外,main()前的全局区定义;
有一个例外,static const int b =0 ;静态的const常量是在类中定义的;
#include <iostream>
using namespace std;
class a
{
public:
static int b;//类中的成员必须在类中声明;
};
int a::b=0;//静态成员变量的定义只能在全局区;
int main()
{
a c;
a::b=3;
cout<<a::b<<endl;
cout<<c.b<<endl;//通过类和对象都能访问;
return 0;
}
1.2 静态成员变量的特点
1、静态成员变量存储在全局静态区,只分配一次内存,生命周期为整个程序;
2、属于整个类而不属于任何对象,该类和该类的所有对象共享使用该静态成员变量,即变量只有这一个,所有类对象和类访问的都是这一个变量;
3、如2所述,类可以直接访问,加上作用域即可;对象直接按成员变量访问即可;
4、静态成员变量也遵循public,protected,private限制,在定义时要注意;这点也是静态成员变量和全局变量的区别之一,而且作为成员,不会与全局变量有命名冲突;
5、如2所述,所有对象都能有静态成员变量,而不用分配对应的内存;
2、静态成员函数
2.1 怎么定义一个静态成员函数
在类中声明时加上static修饰即可,其余和普通成员函数一致;
2.2 特点
1、静态成员函数属于类,不属于任何对象,
2、因此没有this指针,也就不能访问任何对象,
3、而普通的成员变量是属于对象的,所以,静态成员函数不能访问非静态成员变量,只能访问静态成员变量;
4、静态成员函数可以通过类名::函数名()的方式调用;也可以通过对象名.函数名()的方式调用
3、总结
静态成员相当于是类这个作用域下的全局成员;