C++知识回顾(1)
一、命名空间
1、namespace的定义
形如“namespace+空间名”的组成命名空间,命名空间解决了函数体等命名冲突的问题。
例子:
namespace Lead_Winder
{
void f()
{
cout<<f()<<endl;
}
}
2、命名空间的融合
如果在一个文件或者将一个项目分在头文件和源文件中使用同一个命名空间,则该命名空间会组合成一个命名空间。
例子:
namespace Lead_Winder
{
void f()
{
cout << "Lead_Winder::f()" << endl;
}
}
namespace Lead_Winder
{
void g(int i)
{
cout << "Lead_Winder::g(int)" << endl;
}
}
上述代码在编译器处理的时候会将其融合成一个命名空间。
如下:
namespace Lead_Winder
{
void f()
{
cout << "Lead_Winder::f()" << endl;
}
void g(int i)
{
cout << "Lead_Winder::g(int)" << endl;
}
}
通过命名空间的使用,我们能够将许多C语言无法做到的实现出来~
3、标准库的命名空间
C++标准库都放在⼀个叫std(standard)的命名空间中。
例子:
int main()
{
std::cout<<"cout是std库内的标准输出函数"<<std::endl;
return 0;
}
4、命名空间的使用:
1、编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。(前提)
2、在一个项目中使用命名空间的方法:
(1)指定命名空间访问。(项目中推荐使用)
namespace A
{
int g()
{
int ret = 1;
return ret;
}
}
int main()
{
cout << A::g() << endl;
return 0;
}
(2)using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式.
namespace A
{
int g()
{
int ret = 1;
return ret;
}
}
using A::g;
int main()
{
cout << g() << endl;
return 0;
}
(3)展开命名空间中全部成员。(项⽬中不推荐,会导致冲突风险较高)
namespace A
{
int g()
{
int ret = 1;
return ret;
}
}
using namespace A;
int main()
{
cout << g() << endl;
return 0;
}
二、类和对象
1、什么是类?
用”关键字+类名“组成一个类(在C语言里叫做struct结构体,但在C++里融合成类)
class Lead_Winder
{
};
2、类的使用
(1)访问限定符
[1] public: 其修饰的成员在类外可以直接被访问 |
---|
[2] private: 只能在类里访问,在类外无法进行访问(可以提供类函数进行访问) |
例1:
class Lead_Winder
{
public:
int f()
{
int ret = 1;
cout << ret << endl;
}
};
int main()
{
Lead_Winder l;
cout << l.f() << endl;
return 0;
}
例2:
class Lead_Winder
{
public:
int Geta()
{
return _a;
}
private:
int _a = 1;
};
int main()
{
Lead_Winder l;
//cout << l._a << endl; -> error(无法访问被private修饰的类私有成员)
//访问方法:在类内部通过成员函数访问
cout << l.Geta() << endl;
return 0;
}
3、类实例化出对象
(1)类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只
是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
例子(顺序表):
class SeqList
{
public:
SeqList()
:_size(4)
,_capacity(_size + 1)
{
_array = new int[_size + 1];
}
private:
int* _array;
int _size;
int _capacity;
};
int main()
{
return 0;
}
在该顺序表中,class SeqList并不会开辟空间,而要在实例化出对象时才会分配其空间。即:
int main()
{
// 类实例化出对象 -> 分配空间
SeqList s1;
return 0;
}
(2)对象大小(内存对齐原则):
class SeqList
{
public:
SeqList()
:_size(4)
,_capacity(_size + 1)
{
_array = new int[_size + 1];
}
private:
int* _array;
int _size;
int _capacity;
};
int main()
{
cout << sizeof(SeqList) << endl;
return 0;
}
通过 sizeof(SeqList) 能够计算出该类的内存大小。其计算方法如下:
• 第⼀个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
• 注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
• VS中默认的对⻬数为8
• 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩
就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
下面,我们来计算一下该Seqlist类的内存大小(见图):
3、类的默认构造函数
(1)构造函数
【1】主要任务:在类实例化出对象时进行初始化对象~
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
return 0;
}
在上述代码中,Date(就是一个没有参数的构造函数),它的作用是类实例化出对象时进行调用进行初始化. Date d进行对象实例化(分配空间时)进行初始化,即d的私有成员的_year = 1900, _month = 1, _day = 1.
【2】构造函数的特点:
函数名与类名相同。
⽆返回值。
对象实例化时系统会⾃动调⽤对应的构造函数。
构造函数可以重载。
如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式定义编译器将不再⽣成。
⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。
但是这三个函数有且只有⼀个存在,不能同时存在。
注意:C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型,⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。
4、析构函数
【1】析构函数的作用:完成对象中资源的清理释放⼯作。
【2】析构函数的特点:
- 析构函数名是在类名前加上字符 ~。
- ⽆参数⽆返回值。
- ⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。
- 对象⽣命周期结束时,系统会⾃动调⽤析构函数。
- 跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,⾃定类型成员会
调⽤他的析构函数。
还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员
⽆论什么情况都会⾃动调⽤析构函数。
例子:
class A { public: A() { _a[0] = 1; _a[1] = 2; _a[2] = 3; } ~A() { delete[] _a; _a = nullptr; } private: int* _a = new int[3]; };
这个例子说明了A()是为了释放通过new动态开辟的资源的清理工作,同时也说明当类里没有资源需要清理时,也可以不写析构函数(例如上面的Date类内无资源需要释放和清理,这时我们就说Date类无需析构函数)
5、拷贝构造函数
【1】析构函数的作用:在C++中,拷贝构造函数是一种特殊的构造函数,用于创建一个新对象作为现有对象的副本。当使用一个对象来初始化同一类的另一个对象时,就会调用拷贝构造函数。
【2】拷贝函数的特点:
拷⻉构造函数是构造函数的⼀个重载。
拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上
会引发⽆穷递归调⽤。 拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,
后⾯的参数必须有缺省值。
C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返
回都会调⽤拷⻉构造完成。
若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成
员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。
像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完
成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。
例子:
class A { public: A() { _a[0] = 1; _a[1] = 2; _a[2] = 3; } A(const A& a) { _a = new int[3]; for (int i = 0; i < 3; i++) { _a[i] = a._a[i]; } } ~A() { delete[] _a; _a = nullptr; } private: int* _a = new int[3]; }; int main() { A aa1; A aa2(aa1); return 0; }
在上述代码中,如果没有A(const A& a)这个拷贝构造函数的话,由于A这个类的成员中有动态开辟的资源,若没有拷贝构造,编译器默认生成的是浅拷贝,这会导致在最后编译器自动调用析构函数时发生报错~
三、this指针
1、C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显
⽰使⽤this指针。
2、编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this
指针。
3、类的成员函数中访问成员变量,本质都是通过this指针访问的。
例子:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
void Init(int year, int month, int day)
{
_year = year;
this->_month = month;
this->_day = day;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
这里的Display()函数中对私有成员的访问其实就是通过隐式this指针访问的,如下:
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
this指针在一些场景中有重要的应用,在接下来的场景中会有介绍~
注意:指针储存在内存的栈区。
month)
, _day(day)
{
}
void Init(int year, int month, int day)
{
_year = year;
this->_month = month;
this->_day = day;
}
void Display()
{
cout << _year << “-” << _month << “-” << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
这里的Display()函数中对私有成员的访问其实就是通过隐式this指针访问的,如下:
```c++
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
this指针在一些场景中有重要的应用,在接下来的场景中会有介绍~
注意:指针储存在内存的栈区。
感谢你的支持,我们下期再会~