class ClassName {
public:
// 公有成员
Type memberVariable; // 数据成员
ReturnType memberFunction(); // 成员函数声明
private:
// 私有成员
Type privateMemberVariable; // 数据成员
ReturnType privateMemberFunction(); // 成员函数声明
protected:
// 保护成员
Type protectedMemberVariable; // 数据成员
ReturnType protectedMemberFunction(); // 成员函数声明
};
Important points
public
:公有访问权限,类的外部可以访问
private
:私有访问权限,只有类的内部可以访问。
protected
:保护访问权限,只有类的内部和派生类可以访问。
注意,默认权限为
private
这里可以实现成员变量的被操作权限
在class中,声明数组大小时,如果声明大小使用的变量为class内的变量时,应当如下
class Map{
public:
const static int maxn =4343;
int next[maxn]={};
};
或者建议使用
array
或者
vector
关于class中的static修饰词警示后人
在class中访问没有static修饰的函数与变量都是需要一个已经创建的对象才可以访问。
但是有了static修饰以后便会有所不同。有static修饰的变量和函数仅仅属于这个类本身,不属于某个特定的对象,但是其仍然拥有访问权限的设置!!
其可以直接被如下方式访问
class classname{ public: static functionname(){/*content*/} static cnt; }; int main (){ classname::functionname(); cout<<classname::cnt<<endl; }
构造函数语法:
类名(){}
析构函数语法:
~类名(){}
注:
这两个函数会被设置访问权限,可以限制类的创建和销毁!!
两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
三种调用方式:
括号法 “““
显示法
隐式转换法
深拷贝与浅拷贝的差别:
浅拷贝是指复制对象时,新的对象和原始对象共享相同的内存地址。换句话说,浅拷贝仅复制对象的值(即指针和基本数据类型的值),而不是指针所指向的实际对象。
特点:
深拷贝则是创建一个新对象,并递归地复制原始对象所引用的所有对象。换句话说,深拷贝不仅复制对象的值,还复制对象内包含的所有指针指向的对象,从而创建一个完全独立的新对象。
特点:
class Person {
public:
////传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化(一定要记着初始化,不然编译错误不显示!!!)
class Example {
public:
static int staticVar; // 声明静态成员变量
};
int Example::staticVar = 0; // 定义并初始化静态成员变量
静态函数可以在类里面声明,在类外定义!!
保证不会改变对象的状态,但是可以调用更改 static 修饰的变量
只可以调用其他const成员函数
为了保证在const成员函数中,不会对对象进行改变
可以调用静态函数!!!
可以返回一个值,但是如果返回的是指针or引用,则必须加上const
class Type{
int a;
const int* GO() const{//int const* GO() const 这样也可以
return &a;
}
};
这一条并不适用于 static变量:
当返回的值为static变量的地址或引用时,可以不用static
注意:在这里在再次区分一下
const int *
int const *
int * const
前两者是一样的,定义的指针不可以修改对应的地址
第三者是指该指针所指的地址不可修改,但是可以通过指针修改内容
成员变量加上
\(mutable\)
就可以不受以上规则限制,而可被const函数修改和返回非常量指针
总结:
常函数保证了不会通过其对对象有任何形式的修改包括指针与引用,但是对应于
\(static,mutabl\)
修饰的变量例外
const关键字的使用方式:
const int* () const{}
前一个
\(const\)
用于修饰返回值,后一个用于修饰函数为静态函数
注意:函数可以反回
\(const\)
的值,但是没有意义,因为其返回的值是拷贝。但是返回
\(const\)
的指针与引用是有意义的。
const MyClass& getObject() const {
static MyClass obj;
return obj;
}
OR
const MyClass* getObject() const {
static MyClass obj;
return obj;
}
通过声明友元的方式,可以使C++中某个类的private和protected的变量和函数被其他类和函数访问
class People{
friend class Dorm; //友元类
friend void get_password(const People&,const Dorm&); //友元函数
public:
const int ID;
void Change_drompassword(const int&,const int&,const int &,Dorm&);
public:
People(int id,int Password): ID(id),password(Password){
cout<<"creat a new person\n";
}
private:
int password;
};
class Dorm{
friend class People;
friend void get_password(const People & a,const Dorm & b);
friend void People::Change_drompassword(const int&,const int&,const int &,Dorm&); //友元类函数
private:
int get_number(){
return dorm_number;
}
int dorm_number;
int dorm_password;
public:
Dorm(int number,int password):dorm_number(number),dorm_password(password){
cout<<"creat a drom!\n";
}
};
Attention:
用于从外部源(如键盘或文件)读取数据。
主要的输入流对象是
std::cin
,它表示标准输入流。
cin.fail()
用于检查输入是否成功
cin.ignore()
函数
忽略指定数量的字符
std::cin.ignore(count);//count 代表数量
忽略直到特定字符或 EOF:
std::cin.ignore(count, delimiter);//delimiter 代表分隔符,这个分隔符也会被舍去
std::cin.ignore(numeric_limits<std::streamsize>::max(), '\n')://忽略输入流中的特定字符或直到遇到换行符。
std::cout
cout.flush()
用于输出错误信息。
主要的错误流对象是
std::cerr
,它用于打印错误信息。
std::clog
注:
以上四个标准流的使用方法都是一样的
int x; std::cin >> x; std::cout << "Hello, World!" << std::endl; std::cerr << "An error occurred!" << std::endl; std::clog << "This is a log message." << std::endl;
除了标准流,C++还支持文件流,用于从文件中读取数据或将数据写入文件。文件流的主要类有:
std::ifstream
std::ofstream
std::fstream
#include <iostream>
#include <fstream> // 引入文件流
int main() {
// 写入文件
std::ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "Hello, File!" << std::endl;
outFile.close();
}
// 读取文件
std::ifstream inFile("example.txt");
std::string line;
if (inFile.is_open()) {
while (getline(inFile, line)) {
std::cout << line << std::endl;
}
inFile.close();
}
return 0;
}
首先在打开文件流是可以设置打开模式的,设置方法如下
std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::ate);
注:
当要使用多个打开模式时,可以用
|
将其链接
接下来介绍文件打开模式
std::ios::in
:用于读操作。
std::ios::out
:用于写操作,文件内容会被覆盖(如果文件存在)。
std::ios::app
:用于追加操作,所有写入的数据会追加到文件末尾。
std::ios::ate
:文件打开时将文件指针定位到文件末尾,适用于需要在文件末尾开始读写的情况。
std::ios::trunc
:如果文件已经存在,会清空文件内容,通常与
std::ios::out
一起使用。
std::ios::binary
以二进制的方式打开文件,不会进行仍何文本转化(比如换行符转化)
#include <iostream>
#include <fstream>
#include <vector>
int main() {
// 文件名
const char* filename = "example.bin";
// 打开文件以二进制模式
std::ifstream file(filename, std::ios::binary);
// 检查文件是否成功打开
if (!file) {
std::cerr << "无法打开文件: " << filename << std::endl;
return 1;
}
// 移动文件指针到文件末尾以获取文件大小
file.seekg(0, std::ios::end);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
// 使用 std::vector 存储文件内容
std::vector<char> buffer(size);
// 读取文件内容到 buffer 中
if (file.read(buffer.data(), size)) {
// 处理文件内容(示例中仅输出文件大小)
std::cout << "文件大小: " << size << " 字节" << std::endl;
// 这里可以根据需要处理 buffer 中的数据
} else {
std::cerr << "读取文件失败" << std::endl;
}
// 关闭文件
file.close();
return 0;
}
/*
文件名:你需要指定你想要读取的文件名。示例中使用的是 "example.bin"。
打开文件:std::ifstream file(filename, std::ios::binary); 这行代码打开了指定的文件,并以二进制模式进行读取。
获取文件大小:使用 seekg 和 tellg 方法来确定文件的大小。
读取内容:file.read(buffer.data(), size); 这行代码将文件内容读取到 buffer 中。
错误检查:在打开文件和读取文件后,检查是否成功执行这些操作。
关闭文件:使用 file.close(); 关闭文件。
*/
以上打开方式在
ifstream
ofstream
中使用是没有问题的,只不过要注意不要把输出的文件打开方式安在输入文件流上面了,
会导致文件流无法正常打开的问题
最后让我们来介绍一下在
fstream
中使用这些文件打开方式会出现的一些问题
单独使用
app,ate,trunc
都是不行的,因为他们没有给出文件的读写模式,所以要加上
in
out
有个作死的玩法
fstream IN(".in", std::ios::out);
cout<<IN.is_open()<<endl;
int a;
IN>>a;
IN<<1111<<endl;
然后你就会发现输出了个寂寞
std::ios::beg //开头指针
std::ios::cur //当前指针
std::ios::end //文件末尾
搭配函数
seekg()
使用
// 将读指针移动到文件开头
inFile.seekg(0, std::ios::beg);
// 将读指针移动到当前指针位置向后偏移5个字符的位置
inFile.seekg(5, std::ios::cur);
这两是流的两种类型,是最基础的,一个是输入流,一个是输出流
重载运算符的时候就是用的这两
//笔者摆烂了,BF5见!
重载函数和其他函数一样都会存在访问权限问题!!!
class Grade{
friend ostream& operator<<(ostream& out,const Grade & P);
private:
int grade_Chnese,grade_program,grade_math;
Grade(int a,int b,int c,bool OP):grade_Chnese(a),grade_program(b),grade_math(c){
if(OP) cout<<"insert grade succesfully!\n";
}
Grade(){}
};
ostream& operator <<(ostream & OUTT,const Cnt & b){
OUTT<<b.cnt<<endl;
return OUTT;
}
注:
在友函数重载中,两个参数分别代表左右操作符(其实也可以不用加
friend
如果不用访问
private
和
protected
的话)
class Grade{
public:
Grade operator + (const Grade& A) const{
return Grade(A.grade_Chnese+grade_Chnese,A.grade_program+grade_program,A.grade_math+grade_math,0);
}
}
注:
在成员函数重载中对象本身会作为左操作数,参数作为右操作数
注:
+ - * / > < >= <= ==
都比较简单,参考结构体重构一样的
注:
建议在定义参数时使用常变量+引用,防止意外的更改以及加快速度
由于在左移右移符号中,对象始终处于右操作数,所以只可以使用友函数的方法
class Grade{
friend ostream& operator<<(ostream& out,const Grade & P);
friend class People;
private:
int grade_Chnese,grade_program,grade_math;
Grade(int a,int b,int c,bool OP):grade_Chnese(a),grade_program(b),grade_math(c){
if(OP) cout<<"insert grade succesfully!\n";
}
Grade(){}
public:
Grade operator + (const Grade& A) const{
return Grade(A.grade_Chnese+this->grade_Chnese,A.grade_program+this->grade_program,A.grade_math+this->grade_math,0);
}
};
ostream& operator<<(ostream& Gut,const Grade & P){
Gut<<P.grade_Chnese<<" "<<P.grade_program<<" "<<P.grade_math<<endl;
return Gut;
}
class Cnt{
public:
double cnt;
Cnt(long double a=0){cnt=a;}
Cnt& operator ++(){
(this->cnt)+=1;
return *this;
}//先修改,后返回引用
Cnt operator ++(int){//这个int用于占位,是C++编译器用于区分这两个重载的标志,无实际意义!!
Cnt a=*this;
cnt+=1;
return a;
}//先返回值,后修改
};
继承的基本概念:
公有继承(Public Inheritance)
:
基类的私有成员不能直接访问。
class Base {
public:
int pubValue;
protected:
int protValue;
private:
int privValue;
};
class Derived : public Base {
public:
void accessMembers() {
pubValue = 1; // 可以访问公有成员
protValue = 2; // 可以访问保护成员
// privValue = 3; // 不能访问私有成员
}
};
2. **保护继承(Protected Inheritance)**:
- 基类的公有和保护成员在派生类中都变成保护成员。
- 不允许外部代码通过派生类访问这些成员,但派生类内部可以访问。
```cpp
class Derived : protected Base {
public:
void accessMembers() {
pubValue = 1; // 可以访问公有成员(现在是保护成员)
protValue = 2; // 可以访问保护成员
// privValue = 3; // 不能访问私有成员
}
};
```
3. **私有继承(Private Inheritance)**:
- 基类的公有和保护成员在派生类中都变成私有成员。
- 外部代码不能通过派生类访问这些成员,但派生类内部可以访问。
```cpp
class Derived : private Base {
public:
void accessMembers() {
pubValue = 1; // 可以访问公有成员(现在是私有成员)
protValue = 2; // 可以访问保护成员
// privValue = 3; // 不能访问私有成员
}
};
注意:
虽然说基类
private
的成员是无法在派生类中被调用的,但是实际上他是被继承过来了的,只是被编译器隐藏了。
构造函数和析构函数
:
class Base {
public:
Base() { std::cout << "Base Constructor\n"; }
virtual ~Base() { std::cout << "Base Destructor\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived Constructor\n"; }
~Derived() { std::cout << "Derived Destructor\n"; }
};
如果你想向基类的构造函数中输入参数,可以用以下方式:
```C++
class People{
public:
string Name;
People(){cout<<"creat a people\n";}
People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
protected:
string ID;
string Phone_num;
};
class Student: public People{
public:
Student(){cout<<"creat a new student\n";}
Student(const string& name,const string& id,const string& phone_num):
People(name,id,phone_num){//在这里,使用参数列表的方式注入参数
cout<<"creat a new student\n";
}
};
这样写是不对的:
class People{
public:
string Name;
People(){cout<<"creat a people\n";}
People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
protected:
string ID;
string Phone_num;
};
class Student: public People{
public:
Student(){cout<<"creat a new student\n";}
Student(const string& name,const string& id,const string& phone_num):
People::Name(name),People::ID(id),People::Phone_num(phone_num){
cout<<"creat a new student\n";
}
};
变量名冲突:
多继承中如果父类中出现了同名情况,子类使用时候要加作用域
class People{
public:
string Name;
People(){cout<<"creat a people\n";}
People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
void Print();
protected:
string ID;
string Phone_num;
};
class Student: public People{
public:
string ID;
Student(){cout<<"creat a new student\n";}
Student(const string& name,const string& id,const string& phone_num,const string & IDD):People(name,id,phone_num),ID(IDD){
cout<<"creat a new student\n";
}
void OKK(){
cout<<People::ID<<" "<<ID<<endl;//这里,加入作用域就ok啦
}
};
虚继承
:
virtual
class Base {
public:
int value;
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Final : public Derived1, public Derived2 {};//在Final 类中就只会有一个 Base::value 避免了冗余和二义性
多重继承
:
class A {
public:
void funcA() {}
};
class B {
public:
void funcB() {}
};
class C : public A, public B {
public:
void funcC() {}
};
继承是C++的一个强大特性,但合理地使用它对于维护代码的可读性和可维护性是非常重要的。
C++中的多态主要有两种类型:
编译时多态发生在编译阶段,主要通过函数重载(Function Overloading)和运算符重载(Operator Overloading)来实现。
虚函数
:在基类中声明为
virtual
的成员函数,允许派生类重写,并在运行时通过基类指针或引用调用派生类的实现。
class Base {
public:
virtual void show() const {
std::cout << "Base class show function" << std::endl;
}
virtual ~Base() {} // 虚析构函数,确保正确释放派生类资源
};
class Derived : public Base {
public:
void show() const override { // 重写基类的 show 函数
std::cout << "Derived class show function" << std::endl;
}
};
这里
override
表示这是一个重写的函数,如果是
override final
指明派生类中某个虚函数不仅是重写了基类的虚函数,而且不允许进一步重写
在基类中也可以采用纯虚函数的写法
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为
抽象类
抽象类特点
:
\(Attention:\)
当派生类的成员占据了堆的空间时(就是派生类是被
new
函数弄个出来的时候),基类的析构函数必须虚函数(纯虚函数也OK),不然会导致在
delete
派生对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,导致出现内存泄漏。
#include <iostream>
//Wrong
class Base {
public:
Base() { std::cout << "Base constructor\n"; }
~Base() { std::cout << "Base destructor\n"; } // 非虚析构函数
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor\n"; }
~Derived() { std::cout << "Derived destructor\n"; }
};
int main() {
Base* basePtr = new Derived();
delete basePtr; // 只调用 Base 的析构函数
return 0;
}
#include <iostream>
//corect
class Base {
public:
Base() { std::cout << "Base constructor\n"; }
virtual ~Base() { std::cout << "Base destructor\n"; } // 虚析构函数
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor\n"; }
~Derived() { std::cout << "Derived destructor\n"; }
};
int main() {
Base* basePtr = new Derived();
delete basePtr; // 现在会调用 Derived 的析构函数,然后调用 Base 的析构函数
return 0;
}