C++第二天

01. 命名空间的使用

#include <iostream>

// 引入命名空间的目的是为了解决命名冲突!!!!

// 定义命名空间的语法: namesapce
namespace MySpace
{
	// 直接的定义一个整型变量
	int number = 100;

	// 还可以定义一个函数,
	void ShowNumber()
	{
		// 函数内可以操作同一作用域的所有变量
		std::cout << number << std::endl;
	}

	// 创建一个类类型
	class CObj
	{
	private:		
		// 只能在当前的类内(成员函数中)访问
		int number;

	public:
		void show()
		{
			// 输出的是当前类内的 number
			std::cout << number << std::endl;
		}
	};
}


int main()
{
	// C++ 提供的所有标准库函数都被包含在了一个
	// 名为 std 的命名空间中,所以在使用时,必须
	// 要指定当前的函数或变量所在的位置 
	std::cout << "Hello World" << std::endl;

	// 如果想要直接的修改作用域中的 number
	MySpace::number = 100;

	// 可以调用到作用域内的函数
	MySpace::ShowNumber();

	// 使用作用域内的类型定义对象,对象
	// 没有被初始化,所以输出没有意义
	MySpace::CObj object;
	object.show();

	return 0;
}

02. 访问命名空间的方式

#include <iostream>

namespace MySpace
{
	// 命名空间内的所有对象默认的访问属性都是 public
	int numberA = 0;
	int numberB = 0;
	// 是一个命名空间中定义的 numberC
	int numberC = 0;
}

// 全局作用域内定义一个 numberC
int numberC = 10;

int main()
{
	// 1. 通过在变量前添加 :: 来指定使用的作用域
	//	- 缺点: 每次都要添加作用域,非常繁琐
	MySpace::numberA = 100;

	// 2. 使用 using 作用域名称::变量名的方式
	//	- 以后使用到的 numberB 默认都是 MySpace 作用域中的
	//	- 缺点是,需要有非常多的 using 指令,使用不便,但是推荐
	using MySpace::numberB;
	numberB = 100;

	// 3. 使用 using namespace 作用域名称 的方式
	// 直接指定默认的作用域为目标位置
	using namespace MySpace;
	// 不推荐使用第三种方式,但是这种方式用的最多
	numberA = numberB = numberC = 100;


	return 0;
}

03. 命名空间的嵌套

// 这是一个外层的命名空间
namespace Outer
{
	// 嵌套的命名空间
	namespace Inner
	{
		// 内层命名空间内的变量
		int number = 0;
	}

	// 外层命名空间内的变量
	int number = 0;
}


int main()
{
	// 访问外层命名空间的变量
	Outer::number = 100;

	// 访问内层命名空间的变量
	Outer::Inner::number = 200;

	// 给 Outer 取一个别名叫做 o 使用更简短
	namespace O = Outer;
	O::Inner::number = 200;

	return 0;
}

04. 对类的复习

#include <iostream>
using namespace std;

// 日期类:用于设置显示和计算日期
class CDate
{
private:
	// 不想直接被外界访问到的数据
	int m_Year, m_Month, m_Day;

	// 返回一个常量字符串
	const char* IsLeapYear()
	{
		if (m_Year % 400 == 0 || (m_Year % 4 == 0 && m_Year % 100 != 0))
			return "闰年";
		else
			return "平年";
	}

public:
	// 设置年月日
	void SetDate(int y, int m, int d)
	{
		m_Year = y;
		m_Month = m;
		m_Day = d;
	}

	// 打印年月日
	void PrintDate()
	{
		// 输出日期
		printf("%d 年 %d 月 %d 日, 这一年是 %s\n",
			m_Year, m_Month, m_Day, IsLeapYear());
	}

	// 获取当前已经过了今年的多少天
	int GetDayCount()
	{
		// 用于保存所有的天数
		int Days = 0;

		// 总结每一个月有多少天
		int Months[12] = { 31, 28, 31, 30, 31, 
			30, 31, 31, 30, 31, 30, 31 };

		// 计算出当前已经过完的月份一共有多少天
		for (int i = 0; i < m_Month - 1; ++i)
			Days += Months[i];

		// 加上当前这个月一共过的天数
		Days += m_Day;

		// 如果是闰年,并且当前月份大于2,就需要加一天
		if (strcmp(IsLeapYear(), "闰年") == 0 && m_Month > 2)
			Days++;

		return Days;
	}
};

int main()
{
	CDate Date;
	Date.SetDate(2008, 9, 3);
	Date.PrintDate();
	cout << "这一年已经过了" << Date.GetDayCount() << "天" << endl;

	return 0;
}

05. 重名错误的产生和解决

class CObj
{
private:
	int numberA;
	int numberB;

public:
	// 用于将传入的参数赋值给数据成员
	void SetDataA(int numberA, int numberB)
	{
		// 从小作用域向外查找到大作用域
		// 实际上是局部变量赋值给了局部
		// 变量,没有改变成员的值
		numberA = numberA;
		numberB = numberB;
	}

	void SetDataB(int numberA, int numberB)
	{
		// :: 前面不加东西表示使用的是全局的
		// this 表示使用的就是当前对象的数据
		this->numberA = numberA;
		this->numberB = numberB;
		// 最好能够避免局部变量和成员重名
	}
};

int main()
{
	CObj obj;
	obj.SetDataA(10, 20);
	obj.SetDataB(10, 20);

	return 0;
}

06. 使用 this 产生的错误

#include <iostream>
using namespace std;

class CObj
{
private:
	// 可能被访问到我的数据
	int number;

public:
	// 没有访问数据成员的函数
	void PrintHello()
	{
		// 这里没有访问 this 指针
		printf("Hello 15PB\n");
	}

	// 访问了数据成员的函数
	void PrintNumber()
	{
		// 下面这里实际转换出来的是,但是实际
		// 使用的时候, this 是一个空指针,访
		// 问空指针会直接产生数据访问异常
		// printf("%d\n", this->number);
		printf("%d\n", number);
	}
};

int main()
{
	// 指向 nullptr 的空指针
	CObj* pObject = nullptr;

	// 分别调用两个函数
	pObject->PrintHello();
	pObject->PrintNumber();

	return 0;
}

07. 使用构造和析构

#include <iostream>
using namespace std;

// 一个类如果什么都没有则被称之为空类,一个空类
// 创建的对象其大小为 1 个字节

// 当一个类没有定义任何一个构造函数,就会自动生成
// 一个无参且没有任何意义的构造函数。一旦定义了任意
// 形式的一个构造函数,默认构造函数就不存在了
// 默认构造函数: CTest() { }
// 也会生成默认的析构函数和拷贝构造函数

// 构造函数:
//	- 用于初始化对象,在对象创建时被自动调用
//	- 函数的名称和类名完全相同
//	- 构造函数一定没有返回值
//	- 构造函数可以有参数,并且可以重载(可以有多个)

// 析构函数:
//	- 通常用于释放对象的资源,对象被销毁时自动调用
//	- 析构函数的名称是在类名前面加上 ~
//	- 析构函数一定没有返回值
//	- 析构函数没有参数,且只能有一个

// 类的构造函数和析构函数
class CTest
{
public:
	CTest() { cout << "这是构造函数" << endl; }
	~CTest() { cout << "这是析构函数" << endl; }
};

// 1. 在为全局范围内定义的变量,如果没有
//	初始化,那么它的值会是 0,并且全局变
//	量的初始化位于 main 函数之前。
//	- 作用域: 从定义的位置开始到文件结束
//	- 生存周期: 从程序开始到程序结束。
CTest g_Test;

int main()
{
	{
		// 2. 在任何花括号内创建的都是局部变量
		//	未经初始化的局部变量保存的是 0xCC,
		//	对应的就是"烫烫烫"。
		//	- 作用域: 从定义开始到花括号结束
		//	- 生命周期: 进入花括号到离开花括号
		CTest l_Test;
	}

	// 3. 在堆空间中创建一个对象,没有初始化
	//	的堆空间保存的是0xcd,对应的是"屯屯"
	//	- 生命周期: 从 new 到 delete 掉
	CTest* h_pTest = new CTest;
	delete h_pTest;

	return 0;
}

08. 构造析构的顺序

#include <iostream>
using namespace std;

class CMonitor 
{
public:
	CMonitor() { cout << " 构造 显示器.\n"; }
	~CMonitor() { cout << " 析构 显示器.\n"; }
};

class CKeyboard 
{
public:
	CKeyboard() { cout << " 构造 键盘.\n"; }
	~CKeyboard() { cout << " 析构 键盘.\n"; }
};

class CComputer 
{
public:
	CComputer() { cout << " 构造 电脑.\n"; }
	~CComputer() { cout << " 析构 电脑.\n"; }
protected:
	CMonitor  m_objMonitor;   // 数据成员是类对象
	CKeyboard m_objKeyboard;  // 数据成员是类对象
};

int main() 
{
	CComputer* com = new CComputer(); //显式调用无参构造
	delete com;
	return 0;
}

// 当一个类中存在其他类对象时,首先会按照成员对象的
// 定义顺序依次调用成员的构造函数,最后调用自己的构
// 造函数: 显示器 -> 键盘 -> 电脑
//	- (成员类对象 -> 当前类对象)

// 析构函数的调用顺序是和构造完全相反的。

09. 构造函数的重载

// 构造函数的重载

class CObj
{
private:
	// 需要进行初始化的两个数据成员
	char chr;
	int number;

public:
	// 无参的构造函数
	CObj()
	{
		// 在任何环境下创建的变量初始值都是 0
		number = chr = 0;
	}

	// 带两个参数的构造函数,可以只输入一个值
	CObj(char chr, int n = 0)
	{
		this->chr = chr;
		number = n;
	}

	// 特殊的情况下只需要输入整数
	// 带两个参数的构造函数,可以只输入一个值
	CObj(int n, char chr = 0)
	{
		number = n;
		this->chr = chr;
	}
};

int main()
{
	CObj obj1;			// Cobj()
	CObj obj2(1);		// CObj(int n, char chr = 0)
	CObj obj3('a');		// CObj(char chr, int n = 0)
	CObj obj4(1, 'a');	// CObj(int n, char chr = 0)

	return 0;
}

0A. 使用初始化列表

1.构造函数: 没有使用初始化列表的
CObj(int number)
{
这个地方 numberA 和 numberB
是已经存在的值,所以执行的是赋
值操作
this->numberA = number;
this->numberB = number;
}

2.// 什么是初始化?
// - 初始化是指在创建对象的同时
// - 给定指定的数值

3.// 什么是赋值?
// - 赋值使用 = 运算符,主要是将一个对象
// - 的值给定到一个[已存在]的对象上。

#include <iostream>
using namespace std;
class CObj
{
private:
    int numberA;
    int numberB;
public:
    // 构造函数: 使用了初始化列表的
    // 初始化列表只能在构造函数中使用
    CObj(int numbera, int numberb)
        // 需要初始化的成员紧跟在函数声明的后面
        // 使用 : 隔开,成员和成员之间使用 , 隔开
        : numberA(numbera), numberB(numberb) { }
    // 这里使用初始化列表执行的是初始化的操作 
    CObj(int number)
        : numberB(number), numberA(numberB) { }
    // 【初始化列表的初始化顺序和数据在类内的定义顺序是一样的,】
    // 这这个例子中,numberA的定义位于numberB之前,所
    // 以在执行初始化列表的初始化时,先使用 numebrB 初
    // 始化 numberA,但是 numberB 的值是无意义(随机的)的,所以
    // 最终 numberA 也是无意义的,
    //接着使用 1 初始化了
    // numberB,所以结果是 numebrA = ?,numberB = 1
};
int n = 10;
int main()
{   n = 10;
    CObj obj(1);
    return 0;
}

0B. 必须使用初始化列表的情况

1.没有无参构造的成员类对象
2. 一个整数类型的常量
3.一个引用类型

int g_number = 10;

class CMember
{
public:
    // 没有无参构造的构造函数
    CMember(int number) { }
};

class CObj
{
private:
    // 必须要进行初始化的变量

    // 没有无参构造的成员类对象
    CMember member{1};

    // 一个整数类型的常量
    const int cnumber = 0;

    // 一个引用类型
    int& rnumber = g_number;

public:
    // member(1) 调用 member 的构造函数,传入1作为参数
    CObj() : member(1), cnumber(10), 
        rnumber(g_number) { }
};

int main()
{
    CObj obj;

    return 0;
}

0C. 转换构造的调用场景

构造函数可以有很多个,析构函数只有一个,拷贝构造函数最多2个
隐式声明的及用户定义的非 explicit 拷贝构造函数与移动构造函数也是转换构造函数。

// 拷贝构造函数的调用情景:
// 1. 用于使用已知对象的值创建一个同类的新对象
// 2. 将对象以值的方式进行参数的传递时
// 3. 将对象以值的方式作为返回值返回时

#include <iostream>
using namespace std;

// 拷贝构造函数是一个特殊的构造函数,它只有
// 一个参数,这个参数的类型是当前类对象的引用。
class CTest
{
public:
    // 构造函数应该都是公有的
    CTest() { cout << "普通构造函数" << endl; }
    CTest(CTest & obj) { cout << "拷贝构造函数" << endl; }
};

// 以值的方式传递一个对象,传递的实际
// 是目标对象的一份拷贝,会调用拷贝构造
CTest func(CTest test)
{
    // 返回的是 tset 的一份拷贝
    return test;
}

int main()
{
    // 直接创建一个对象
    CTest test;                 // 构造函数

    // 使用同类型的对象初始化一个对象
    CTest test1(test);                      // 拷贝构造
    CTest test2 = test;                     // 拷贝构造
    CTest* ptest3 = new CTest(test);        // 拷贝构造

    // 值传递调用拷贝构造
    func(test);

    return 0;
}

0D. 深拷贝和浅拷贝

// 为什么编译器提供了拷贝构造函数我们还需要重新实现?
// - 当没有提供拷贝构造时,默认生成的拷贝构造使用的
// - 是浅拷贝,浅拷贝就是简单的赋值,可能会产生问题。

// - 深拷贝可以用于解决问题,深拷贝指的时拷贝指针指向
// - 的空间内保存的内容。

// 只要涉及到了内存(指针)的操作就会需要深拷贝

#include <iostream>


class CObj
{
private:
    // 一个指针,指向申请出的堆空间
    char* name;

public:
    // 构造函数,提供一个字符串拷贝到类中
    CObj(const char* n)
    {
        // 1. 获取名称的大小
        int length = strlen(n) + 1;

        // 2. 申请堆空间
        name = new char[length];

        // 3. 进行拷贝
        memcpy(name, n, length);
    }

    // 自定义的拷贝构造是深拷贝
    CObj(CObj& obj)
    {
        // 1. 获取名称的大小
        int length = strlen(obj.name) + 1;

        // 2. 申请堆空间
        this->name = new char[length];

        // 3. 进行拷贝
        memcpy(name, obj.name, length);
    }

    // 析构函数
    ~CObj()
    {
        // 使用默认的拷贝构造时,因为是
        // 浅拷贝,所以只是简单的指针赋
        // 值,任何一个对象在析构时,会
        // 释放堆空间,导致另外一个对象
        // 的指针,指向的是一个已经被释
        // 放的空间,是一个悬空指针。

        if (name != nullptr)
            // 申请和释放应该对应
            delete[] name;
    }
};

int main()
{
    CObj* obj = new CObj("xiaoming");

    // 调用的是默认 的拷贝构造
    CObj* obj2 = new CObj(*obj);

    delete obj;
    delete obj2;


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值