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;
}