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;
}
懒汉模式:
#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;
}
posted @ 2018-08-08 19:21  LyndonMario  阅读(405)  评论(0编辑  收藏  举报