名字空间
namespace 的基础与扩展
cpp
#include <iostream>
using namespace std;
namespace ns
{
int a = 3;
float f(int x)
{
return x / 2;
}
namespace ns_ns // 可以进行嵌套
{
int a = 6;
float f(int x)
{
return x / 3;
}
}
}
namespace ns
{
int b = 5; // 直接扩充成员
}
int main()
{
cout << ns::a << endl; // 使用自创的名字空间,必须要告知来源
cout << ns::f(6) << endl;
cout << ns::ns_ns::a << endl; // 同样的,嵌套使用必须说明来源
cout << ns::ns_ns::f(6) << endl;
cout << ns::b << endl;
return 0;
}
若两个命名空间出现相同名字,则只需在调用时明确告知来源即可,
如:假设存已命名空间 name 内部包含 a,则
cout << ns::a << endl;
cout << name::a << endl;
面向对象三大特征
封装
把对象的属性和功能结合成一个独立的系统单位。(尽可能隐藏内部细节,对外形成屏障)
如:人使用电脑,只需要知道键位功能,不需要知道键位是如何绑定函数调用的。
继承
若类的对象A拥有另一个类B的全部属性与服务,称作类A对类B的继承。
如:机动车共有特性发动机,而小汽车、摩托车等都继承了该共有属性。
多态
指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个 特殊类中具有不同的语义
类的封装
类与对象的定义
相似于 c 语言的结构体,但存在以下区别:
1、含有作用域限定符
2、含有函数,也就是类的自身行为(类方法)
cpp
#include <iostream>
using namespace std;
class A
{
public: // 若不添加,默认是 private
int a;
private:
// 类方法:类中的函数
void show()
{
cout << a << endl;
}
};
int main()
{
A a1;
a1.a = 10;
a1.show(); // 通过类产生的对象来调用方法
return 0;
}
类成员权限限定符
公有成员(public
):任何地方都可以访问
保护成员(protected
);只能在本类或其他后代类中访问
私有成员(private
);只能在本类中访问
cpp
一般原则:
1、类数据属性,设定为私有;
2、类方法,设定为公有;
#include <iostream>
using namespace std;
class Kitty
{
public:
void eat() { cout << "吃饭" << endl; }
void sleep() { cout << "睡觉" << endl; }
void actCute() { cout << "卖萌" << endl; }
void f() { cout << "父类 f" << endl; }
};
class A : public Kitty
{
public:
void f() { cout << "子类 f" << endl; }
};
int main()
{
Kitty myCat;
myCat.eat();
myCat.sleep();
myCat.actCute();
myCat.f(); // Kitty::f();
A a;
a. f (); // A::f(); 在本来对象可以简写调用
a. Kitty::f (); // 此时在子类调用从父类继承下来的方法,只需要明确说明即可
return 0;
}
注意:被 protected 和 private 修饰,不可以被类直接调用
类的构造函数
概念:类的特殊成员,专门用于初始化类的各项数据(分配内存、变量初始化)
特点:
1、任何类都必然有至少一个构造函数,可以重载
2、若类显式定义构造函数,则默认无参构造函数会被取消(调用系统默认构造函数,这样就无法得知构造函数干啥。所以不要使用系统的默认构造函数);若无显示定义构造函数,则系统会自动添加一个隐式构造函数
3、每当分配一个类对象时,构造函数会被自动调用
4、构造函数无返回值类型(不是void
),且必须与类名相同
5、构造函数不被设置为静态成员函数,构造函数的函数首部之前或之后不能用const
修饰
cpp
#include <iostream>
using namespace std;
class Node
{
public:
// 特别注意:下面这种情况,两种构造函数都写了,那么就看对象创建时是否带参数
// 若默认无参构造函数未显式写出,而类对象创建时不带参数,那么就会报错,因为没有匹配的构造函数
Node() { cout << "我是默认构造函数" << endl; }
Node(int a) { cout << "我是显式的构造函数,我的编号是:" << a << endl; }
Node(int a, float b); // 函数重载
private:
string name; // 不可以直接赋值
int high;
};
int main()
{
// 动态分配会在堆区,而不会出现在栈区,这就是需要动态分配的原因
Node node;
// Node *node = new Node; //普遍应用的一种方式,通过 new 可以调用构造函数,这是 malloc 不具备的(本质就是 new 除了会分配内存,还会调用构造函数进行变量初始化,而 malloc 只能分配内存,不能初始化)
return 0;
}
小小认知:( 变量 )
初始化:本质是为变量申请开辟内存空间,并没有直观的体现
赋值:将数值存储在已初始化变量上,就是存储在内存空间中
所以,构造函数的变量初始化是为了更加细化的给变量分配内存空间
this 指针
cpp
#include <iostream>
using namespace std;
class Node
{
public:
Node(string name) { this->name = name } // 用来区分哪个是传参,哪个是本身,当然可以不用相同的名字
// 若要返回本身
Node f() { return *this; }
private:
string name; // 不可以直接赋值
int high;
};
int main()
{
Node node("老六");
return 0;
}
类的析构函数
概念:类的特殊成员,专门用于处理类对象被释放时的收尾工作
特点:
1、任何类有且仅有一个析构函数,不可以重载
2、若类显式定义析构函数,则默认无参构造函数会被取消;若无显示定义析构函数,则系统会自动添加一个隐式析构函数
3、每当释放一个类对象时,析构函数会被自动调用
4、构造函数无返回值类型(不是void
),也没有参数,且必须与类名相同,且在前面多一个波浪号
5、如果类对象的作用域相同,那么销毁时析构函数的执行顺序与构造函数相反
cpp
#include <iostream>
using namespace std;
class Node
{
public:
Node() { cout << "我是构造函数" << endl; }
~Node() { cout << "我是析构函数" << endl; }
};
int main()
{
Node *node = new Node;
delete node;
return 0;
}
拷贝构造函数
一种特殊的构造函数,其作用是在构造对象时,复制其他对象的所有成员。
1、拷贝构造函数,要求形参中有一个当前类的类对象
2、若没有就使用默认,若存在则使用存在的,特别需要注意的是:显式的拷贝构造函数基本不会影响已存在的默认拷贝构造函数,除非提供了单参构造函数,且参数类型是类类型的引用
3、拷贝分为浅拷贝和深拷贝,浅拷贝只拷贝值,深拷贝还会拷贝内存(这样也就不会导致重复释放同一个空间)
4、若显示声明和定义拷贝构造函数后,类中的的所有成员变量需要手动拷贝
5、当使用一个老对象去构造一个新对象时会调用拷贝构造函数,如果没有显示声明和定义拷贝构造函数时,类中的非指针类成员的值可以拷贝。默认拷贝构造函数负责执行了值的拷贝
三种应用场景:
(1)用一个老对象构造一个新对象,或者新对象赋给老对象
(2)当函数的形参是一个非引用的类对象时,形参对实参的传递时
(3)函数返回一个非引用的类对象时
cpp
#include <iostream>
using namespace std;
class Node
{
public:
Node(int a) { cout << a << endl; }
Node(const Node &p) { cout << "调用拷贝构造" << endl; }
};
int main()
{
Node *node = new Node(1);
Node *node1 = node; // 会自动调用拷贝构造函数
delete node;
delete node1; // 这会导致 double free ,所以需要进行深拷贝(将内存空间也要拷贝一份)
return 0;
}
-------------------------------------------------- 深拷贝与浅拷贝 --------------------------------------------------
系统默认拷贝构造是浅拷贝,也就是相当于取别名,复制多个指针,但还是指向同一块内存空间。这样就会造成一个结果:重复释放了某一块内存空间,所以一般需要深拷贝
以下重点讲解怎么进行深拷贝:
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A() { p = new char[100]; }
~A() { delete p; }
// 将拷贝构造函数进行重写,另外需要开辟空间,改造成深拷贝
A(const A &r)
{
if(r.p != nullptr)
{
p = new char[100];
memcpy(p, r.p, 100);
}
else
p = nullptr;
}
private:
char *p;
int a;
}
空类中默认的类方法
无参构造函数 析构函数 拷贝构造函数 赋值运算符函数
const 成员
const
基本语义就是将其修饰的符号作只读化处理(不可修改)。
cpp
#include <iostream>
using namespace std;
class person
{
public:
person();
~person();
private:
const unsigned int ID; // const 修饰类成员变量
public:
void studentInfo() const; // const 修饰类方法
};
// 方式一:类构造函数初始化列表中初始化
person::person(unsigned int id)
: ID(id)
{
}
// 方式二:类定义语句中初始化
class person
{
private:
const unsigned int ID = 12345;
};
注意:
1、方式一可以通过构造函数传递不同的参数来给 ID 不同初始值 ---- 适用场景:学生的学号
2、方式二则是所有对象被构造出来的 ID 都是一样的且不可修改 ---- 适用场景:学生的学校
-------------------------------------------------- 实际代码展示 ---------------------------------------------------
#include <iostream>
using namespace std;
class Student
{
private:
const string schoolName = "粤嵌"; // 所有成员都在这个学校
const int ID;
public:
Student(int id)
: ID(id) // 所有类对象都会有不同的 ID
{
}
// 打印学生信息
void showInfo() const // 提高程序运行效率,只要确保该函数不会修改数据,就可以使用 const
{
cout << "我的学校" << schoolName << endl;
cout << "我的ID" << ID << endl;
}
};
int main()
{
Student *luo = new Student(1);
luo->showInfo(); // 重要认知:类成员方法是可以直接使用类成员属性的,并不需要传参。
return 0;
}
注意:
1、类方法的 const 关键字,要放在函数参数列表之后
2、在类方法 声明 和 定义 中都需要加上 const ,因为这是重载的依据之一
3、被 const 修饰的类方法不能访问类的非 const 方法,也不能对该类的其他数据成员赋值
类静态成员的设计和语法
当类成员被static
修饰时,被称为静态成员。(本质:限制其作用域范围)
类成员数据:表达类的属性,而不是具体对象的属性
类成员方法:表达类的行为,而不是具体对象的行为
cpp
#include <iostream>
using namespace std;
class Student
{
private:
// 个人信息(单个对象属性)
unsigned int ID;
string name;
// 群体信息(类类型本身的属性)
static int total; // 总人数
public:
Student() { total++; }
~Student() { total--; }
// 获取学生总人数
static void totalStudentNumber(); // 不依赖于类而存在
{
cout << "总人数:" << total << endl;
}
};
// 静态数据成员,必须在类外定义
int Student::total; // 本质:将全局的作用数据修改其作用域
void totalStudentNumber();
{
cout << "总人数:" << total << endl;
}
int main()
{
Student jack;
Student::totalStudentNumber(); // 可以不依赖对象调用
jack.totalStudentNumber(); // 也可以用类对象调用
return 0;
}
注意:
1、静态类成员( 静态数据区 )与普通成员存储空间不一样
2、静态类成员不属于任何一个对象,是共有的,只有一份
3、静态类方法只能调用其他静态类方法或引用类静态数据,不能调用普通方法和数据(因为后者有归属者,说不定不存在)
小补充:当构造函数被private修饰-------设计模式:单例模式
class Node
{
public:
static Node* getInstance();
private:
Node();
private:
static Node* m_ptr;
};
Node* Node::m_ptr = NULL;
Node::Node()
{
cout << "123" << endl;
}
Node* Node::getIstance()
{
if(m_ptr == NULL)
{
m_ptr = new Node; // 这样设定就是为了只被构造一次
}
return m_ptr;
}
int main()
{
Node* n = Node::getInstance();
cout << n << endl;
return 0;
}
初始化列表
cpp
#include <iostream>
using namespace std;
class Score
{
private:
float math; // 数学成绩
float history; // 历史成绩
public:
Score(float m=0, float h=0)
: math(m), history(h)
{
}
};
class Student
{
private:
int age; // 普通类成员
const string name; // const型类成员
Score score; // 类对象成员
public:
Student(int a, string n, Score &s) // & 这里采用引用就只构造一次
: age(a), name(n), score(s) // 初始化列表
{
}
showInfo() { cout << "数学和历史:" << math << history << endl; }
};
int main()
{
Score score(98, 89);
Student student(22, "罗宏斌", score);
student.showInfo();
return 0;
}
const 型类成员和类对象成员必须在初始化列表中进行初始化,为免去记忆,全部在初始化列表中初始化
类组合
#include <iostream>
using namespace std;
class AAA
{
public:
AAA() { cout << "AAA" << endl; }
~AAA() { cout << "~AAA" << endl; }
private:
int m_AAA;
};
class A
{
public:
A() {cout << "A" << endl;}
~A() {cout << "~A" << endl;}
private:
int m_A;
};
class B
{
public:
B(int n) : m_B(n) {cout << "B" << endl;}
~B() {cout << "~B" << endl;}
private:
int m_B;
};
class C
{
public:
// 不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。
C(int x, int y)
: b(x), m_C(y)
{
cout << "C" << endl;
}
~C() {cout << "~C" << endl;}
private:
A a;
B b;
AAA* aaa;
int m_C;
};
int main(int argc, char *argv[])
{
// 类对象产生时,其构造顺序:
// 1、先构造成员变量
// 2、再调用构造函数
C c(11, 12);
return 0;
}