2.名字空间和构造函数
一.名字空间
C语言中的名字空间:全局,局部,块
C++认为:全局空间用起来非常方便,但是如果把太多的东西放在全局空间中,会造成命名冲突,所以C++引用了这样一种机制,把全局空间在进行划分
名字空间:把全局的命名空间进一步分割。可以创建出一个个独立的命名空间,防止相互之间冲突。
1.定义名字空间:
namespace name
{
变量;
函数;
结构,类;
}
2.名字空间的合并:
a.同名的名字空间会自动合并
b.在同一个命名空间中的标识符必须是唯一的
3.名字空间的定义和声明分开
namespace n1 //在名字空间中声明函数
{
void func(void);
}
void n1::func(void) //在名字空间外定义函数
{
}
4.然后使用名字空间中的内容
a.域限定符(::),直接使用,名字空间::标识符,这样的好处是绝对不会冲突会麻烦一点
b.using namespace 名字空间名;
功能:把名字空间中的标识符对之后的代码全部公开
c.using 名字空间::标识符,标识此标识符对后面的代码公开
5.名字空间的嵌套
名字空间可以嵌套,但是用的时候要逐层解析
namespace n1
{
int num = 10;
namespace n2
{
int num = 20;
namespace n3
{
int num = 30;
}
}
}
n1::n2::n3::num == 30;
n1::n2::num == 20;
n1::num = 10;
using namespace n1::n2;
6.全局空间归属到匿名空间
在全局空间定义的标识符都属于这个匿名空间,匿名空间默认对所有的位置都开放。
如果函数中有同名的标识符把匿名空间中的屏蔽了,可以使用空的域限定符表示它。
ps:不同命名空间的同名函数不构成重载,同一作用域下的同名函数叫重载。
二.class
1.C++中的class和struct,几乎没有任何区别一样,是一种复合数据类型。
2.里面可以有变量用来表达属性,函数用来表示行为。
3.C++中的class和struct,几乎没有任何区别。
4.struct 中的默认访问属性是public,class中的默认访问属性是private
5.C++中默认使用class,和C以示区分
三.class的构造函数
1、在创建对象时自动调用的函数,在整个对象的生命周期中一定会被调用一次,且只能被调用一次。
2、在构造函数中负责对成员变量的初始化、分配资源、设置对象的初始状态。
3.构造函数可以有很多版本,这些不同的版本之间会构成重载,创建对象时,方法不同,所给参数不同,会调用相应的构造函数
如果调用的构造函数不存在,可能会造成编译错误
// 无参构造
Student stu <=> Student* stup = new Student;
Student stu(参数列表) <=> Student* stup = new Student(参数列表);
4.如果类中没有定义构造函数,编译器会自动生成一个无参构造函数
一旦定义了其他版本的构造函数,无参构造函数就不会再生成了,因此为了防止无参方式创建对象出错,再定构造函数时,至少要实现两个
5.无参构造未必无参,在C++中函数可以有默认参数,如果有参构造全部设置了默认参数,就会和无参构造有冲突,它们两个只能存在一个。
6.所谓的"编译器生成的某某函数"
"编译器生成的某某函数",不是真正意义上的函数,编译器作为指令的生成者,只要生成具有某些函数功能的指令即可,没有必要生成高级语言意义上的函数
7.什么时候调用无参构造函数
a.Student stu <=> Student *stup = new Student;
b.创建对象数组,每个对象都会调用一次无参构造
c.如果类A中有成员是类B,当执行完类A的构造函数前,就会自动调用类B的无参构造
d.在类A中,如何调用类B的有参构造
类A(参数列表):成员B(参数列表)
8.类型转换构造函数
用一种数据给对象初始化,默认会自动调用构造函数,达到类型转换的效果
这种方式虽然使用方便,但是会容忍一定的错误存在,如果想让代码的检查更为严格,可以使用explicit关键字禁止隐式转换的方式调用构造函数
9.也可以实现自动类型转换构造函数(默认)
练习:
1、写一个Date类,有属性:年、月、日,实现其各种构造函数。
2、写一个Timer类(属性定义为私有,方法定义为公开)
有一个属性 usigned int second;//second记录定时器的秒数
有一个属性 Action action;//定时器响应动作
typedef bool (*Action)(void *);
有一个方法setAction(Action a),当调用begin()方法之后,second秒之后自动调用a函数。
四、拷贝构造函数
1.是一种特殊的构造函数,就是用一个已有的对象去构造其同类的副本对象,即对象克隆
class 类名
{
类名(类名 &that)
{
//对成员挨个赋值
}
}
2.编译器会默认生成一个拷贝构造函数
编译器生成的拷贝构造函数默认会逐字节复制类中的每一个成员。
如果类A中有类B成员,会在类A的拷贝构造中自动调用类B的拷贝构造
3.程序员可以自定义拷贝构造来取代默认的拷贝构造
a.拷贝构造只能有一个,不能重载
b.一旦程序员自定义了拷贝构造函数,编译器就不再生成了
c.在自定义的拷贝构造中能通过编码来实现成员的复制
4.一般情况下,编译器生成的拷贝构造函数完全够用,不要轻易自己定义拷贝构造
5.什么情况下调用拷贝构造函数:
a.对象给对象赋值
b.用对象给函数传参
c.用对象当作返回值
.初始化列表
1.是一种成员的初始化方式,在构造的函数的大括号体前使用小括号对类的成员进行初始化
class 类名
{
类名(参数列表):成员(参数),成员(参数)...
{
}
}
a.参数列表可以解决构造函数的参数于成员重名
b.参数列表会优先于构造函数先执行
2.如果有成员是类,可以使用{}进行初始化
3.如果有成员是类,可以在初始化列表中显示的调用构造函数
4.如果成员中有const或引用成员,必须使用初始化列表
5.类成员的构造顺序与初始化列表无关,而是与成员定义的顺序有关
五.this指针
1.相同类型的对象各自拥有独立的成员实例,彼此共享一段代码段
2.为了让成员函数知道是哪个对象在调用,并准确访问到对象的成员,编译器会自动为每一个成员函数添加一个看不到的参数,这个参数就是指向对象的指针(this)
3.基本上类中的所有成员函数都有this指针,包括,拷贝,构造,析构,拷贝构造等。
只是构造函数中this指向的是正在被创建的对象。
4.this指针默认情况下都是隐藏的(在成员函数访问成员变量时自动就加上了),但是也可以显示使用
5.什么情况使用this
a.区分成员变量与参数
b.把对象当作返回值与其他对象进行交互
六.常对象与常函数
1.创建对象的时候前面加上const关键字,这个对象就不可再修改,就有了常属性,就意味着整个对象中的所有东西都不能改
2.常对象不能调用普通成员函数,调用成员函数就相当于把对象的this指针给了它,就会有被修改的风险
3.函数体前加const的叫常函数,常对象只能调用常函数,普通对象也能调用常函数
常函数就相当于对this指针添加了const属性
4.常函数与‘非’常函数会形成重载不会冲突
5.如果有成员确实需要修改,但是这个对象又被const修饰,可以对成员添加关键字mutable,这样即使是常对象调用了常函数依然可以修改成员
七.析构函数
1.当对象被销毁时自动调用的函数叫析构函数,对象的整个生命周期只能被调用一次,它是对象被销毁前的最后一个动作。
class 类名
{
//不能重载只能有一个
//不可以有返回值,不可以有参数
~类名(void)
{
}
}
2.编译器会默认产生一个析构函数,默认析构函数负责销毁看的到的类成员,如果有成员是类,会自动调用其析构函数,且类成员的析构过程和创建过程是相反的
3.析构函数虽然不能重载,但可以自定义,自定义了析构函数,默认的析构函数就不会生成了。
4.当类中有析构函数看不到的资源时(new/delete),有需要还原的设置(打开的文件关闭/把获取到的数据保存),这就需要自定义析构函数
练习:实现一个学生类,实现其构造,拷贝构造函数,当对象被销毁时,自动写入stu.txt文件中。
八.赋值构造
1.一个对象给另一个对象赋值的时候调用的函数
Student stu2 = stu1;//拷贝构造
stu2 = stu1;//赋值构造
void func(Student stu); // 拷贝构造
func(stu1);
2.赋值构造的格式
void operator = (Student &that)
{
}
//可以与其他对象进行交互
Student &operator = (Student &that)
{
}
3.编译器会默认生成赋值构造,它的功能与拷贝构造的功能一样,把对象A完全拷贝给对象B
4.赋值构造与拷贝构造区别
赋值构造是在两个对象都已经创建好的情况下,完成的两者间的赋值
而拷贝构造是使用对象A去创建出对象B(对象B还不存在)
如果对象中有常成员,拷贝可以调用,赋值不可以。
5.一般情况下,默认的赋值构造基本够用,除非有成员是指针指向了额外的内存空间,这种情况下才需要自定义拷贝构造和赋值构造。
6.自定义赋值构造
a.确定赋值构造的格式
b.防止自赋值
c.释放旧资源
d.分配新的资源
e.拷贝新内容
f.代码复用(调用拷贝构造函数)
九.静态成员与静态函数
1.类成员被static修饰后,就存储在bss段(由编译器存放,大小固定的),在程序中,动态的创建对象时,静态成员就无法创建,所有的类对象共享一个静态成员。
2.静态成员只能在类中声明,不能在类中定义(必须在类外定义)
类型 类名::成员名;
3.静态成员就是声明在类中的全局变量,在任何位置都可以使用类名::静态成员名进行访问
4.静态成员函数,类中的成员函数被static修饰了以后就变成了静态成员函数,所有的对象共享一份静态成员函数
静态成员函数不会传递this指针,也就不能访问成员变量
5.静态成员函数不会传递指针,也就不能访问成员变量
不通过对象也能调用静态成员函数
类名::静态成员函数(参数)
十.单例模式
1、只能创建出一个对象的类,这种类就叫作单例类,这种模式就叫作单例模式。
2、为什么需要单例模式,是为了提高安全性和稳定性的技巧。
只允许存在唯一对象实例
单例模式的商业应用:
网站计数器
日志管理系统
连接池、线程池、内存池
3、获取对象实例的专门方法
a、全局变量的定义不受控制,能防君子不能防小人
b、专门方法是类的一部分,"我是类型我做主",
借助类禁止在外部创建对象,仅在类内部提供获取对象的接口。
4、如何实现单例模式
a、禁止在类外部创建实例,私有所有的构造函数 private
b、类自己维护其唯一实例,
静态成员变量 static 类名 instance;
静态成员指针 static 类名* instance;
c、提供访问该实例的方法,静态成员函数getInstance()
5、饿汉单例模式
不管是否需要对象都已经创建好了。
优点:效率高、速度快、稳定。
缺点:浪费资源,不管需不需要对象都已经创建好;
6、懒汉单例模式
当首次使用获取对象时才会真正创建出对象。
优点:节约资源
缺点:效率低,速度慢,不安全(多线程情况下)。
饿汉模式:
#include <iostream>
using namespace std;
class Singleton
{
static Singleton intance;
Singleton()
{
}
Singleton(Singleton& that)
{
}
void operator = (Singleton& that)
{
}
public:
static Singleton& getIntance(void)
{
return intance;
}
};
Singleton Singleton::intance;
int main()
{
Singleton& s = Singleton::getIntance();
cout << &s << endl;
Singleton& s1 = Singleton::getIntance();
cout << &s1 << endl;
}
x
28
1
36
1
#include <iostream>
2
3
using namespace std;
4
5
class Singleton
6
{
7
static Singleton intance;
8
Singleton()
9
{
10
11
}
12
Singleton(Singleton& that)
13
{
14
15
}
16
void operator = (Singleton& that)
17
{
18
19
}
20
public:
21
static Singleton& getIntance(void)
22
{
23
return intance;
24
}
25
};
26
27
Singleton Singleton::intance;
28
29
int main()
30
{
31
Singleton& s = Singleton::getIntance();
32
cout << &s << endl;
33
Singleton& s1 = Singleton::getIntance();
34
cout << &s1 << endl;
35
}
36
懒汉模式:
#include <iostream>
using namespace std;
class Singleton
{
static Singleton* intance;
Singleton()
{
}
Singleton(Singleton& that)
{
}
void operator = (Singleton& that)
{
}
public:
static Singleton& getIntance(void)
{
if(NULL == intance)
{
intance = new Singleton;
}
return *intance;
}
~ Singleton(void)
{
delete intance;
}
};
Singleton* Singleton::intance = NULL;
int main()
{
Singleton& s = Singleton::getIntance();
cout << &s << endl;
Singleton& s1 = Singleton::getIntance();
cout << &s1 << endl;
}
1
44
1
#include <iostream>
2
3
using namespace std;
4
5
class Singleton
6
{
7
static Singleton* intance;
8
Singleton()
9
{
10
11
}
12
Singleton(Singleton& that)
13
{
14
15
}
16
void operator = (Singleton& that)
17
{
18
19
}
20
public:
21
static Singleton& getIntance(void)
22
{
23
if(NULL == intance)
24
{
25
intance = new Singleton;
26
}
27
return *intance;
28
}
29
~ Singleton(void)
30
{
31
delete intance;
32
}
33
};
34
35
Singleton* Singleton::intance = NULL;
36
37
int main()
38
{
39
Singleton& s = Singleton::getIntance();
40
cout << &s << endl;
41
Singleton& s1 = Singleton::getIntance();
42
cout << &s1 << endl;
43
}
44