文章目录
声明与定义
如果要声明一个类,标准的做法是声明同名的.h
与.cpp
文件。
.h
存放类的的声明(比如类方法的入参,返回值类型的声明等).cpp
存放类的定义(比如类方法的具体实现)
存放类实现的.cpp
必须include
对应的声明文件.h
,比如car.cpp
必须在开头引入#include "car.h"
.
使用类的地方也需要include
对应的声明文件.h
,比如people.cpp
要使用car
对象的方法必须在开头也引入#include "car.h"
.
为什么要分开存放.h
和.cpp
?
基于模块化的方式考虑,接口实现与接口定义是分开的。另外,分开也可以减少编译器的工作量,至于为什么,你得去了解C++的编译器了。
一般标准库大多采用.h
和.cpp
分离的方式去写的。
enum
枚举类是为了定义一个较小集合同类常量,防止常量误用,可读性也更强。
#include <iostream>
using namespace std;
enum class Color{
red,blue,green
};
Color& operator++(Color& t)
// prefix increment: ++
{
switch (t) {
case Color::red:
return t = Color::blue;
case Color::blue:
return t = Color::green;
case Color::green:
return t = Color::red;
}
}
ostream &operator<<(ostream &output,Color& t)
// prefix increment: ++
{
switch (t) {
case Color::red:
output<< "red";
return output ;
case Color::blue:
output<< "blue";
return output ;
case Color::green:
output<< "green";
return output ;
}
}
int main(){
// 输出枚举类的int值
cout<< (int)Color::red << endl; // => 0
cout<< (int)Color::blue << endl; // => 1
// 比较枚举类
if (Color::red > Color::blue){
cout<< "red is great than blue" << endl;
}else{
cout<< "blue is great than red" << endl; // this is right
}
// 默认枚举类,只有==,<等操作符,要想定义更多,得用运算符重载
Color t= Color::red;
cout<< ++t << endl;
}
输出:
0
1
blue is great than red
blue
对象
因为面向对象学的主要是Java,所以以Java的视角来看待C++的面向对象实现。
对象和指针的区别
我看一个人写的还挺好:C++类对象和类指针的区别
静态方法和成员方法
和Java不同,c++允许对象引用调用静态方法。但是和Java相同的是,不能利用静态方法使用成员变量。
class List{
public:
string hello(List& a);
static string hello();
int sample=0;
};
// 成员方法
string List::hello(List& a) {
cout<< "static sample"<< this->sample << endl; // => allowed
return "hello world";
}
// 静态方法
string List::hello() {
// cout<< "static sample"<< this->sample << endl; // =>not allowed
return "hello world 2";
}
int main (){
List a;
cout<< "greeting:"<< a.hello(a) << endl;
cout<< "greeting:"<< a.hello() << endl;
}
对象的生命周期
以下使用Circle对象为例子,C++的对象分配分成两种情况:
- 类对象分配函数栈,超出作用域,自动释放。
- 类指针指向的对象分配在堆中,需要手动释放(
free
ordelete
)。
class Shape{
public:
int simple;
virtual string hello() = 0;
};
class Circle: public Shape{
public:
Circle(){
cout<<"I am created"<<endl;
simple=0;
}
string hello() {
return "hello";
}
~Circle(){
cout<< "I am killed" << endl;
}
};
void func1(){
// Circle c,类对象,因为在栈上分配,函数执行完,内存就被回收了,执行了析构函数
cout<< "---------func1--------------" << endl;
Circle c;
cout<< c.hello() << endl;
}
void func2(){
// Circle c,类对象,因为在堆上分配,函数执行完,内存不会自动回收,需要使用delete语句手动释放其内存
cout<< "---------func2--------------" << endl;
Circle* c=new Circle();
cout<<c->hello()<<endl;
delete c;
}
int main() {
func1();
func2();
return 0;
}
输出:
I am created
hello
I am killed
---------func2--------------
I am created
hello
I am killed
拷贝构造函数
拷贝构造函数分成三种情况:
- 初始化的时赋值语句
- 作为函数形参传递入函数
- 作为函数返回值返回
1. 第一种情况:初始化的时赋值语句
class Arr {
public:
int* list;
Arr();
};
Arr::Arr() {
cout<< "I am created"<<endl;
this->list=new int[10];
for(int* p=list,i=0;p< list+10; p++,i++){
*p=i;
}
}
int main() {
Arr s;
Arr t =s; // 初始化的时赋值语句
//Arr t(s); // 等价于 Arr t =s;
cout<< "list[0]="<<t.list[0] << endl;
t.list[0]=110;
cout<< "after t changed the first element,s[0] is changed to :"<< s.list[0] << endl;
}
这里会调用默认的复制构造函数,将对象成员一对一拷贝过去,在这个例子中,只是将指针拷贝了一份,但是数组的内存还是一份。
move函数
防止内存拷贝
int main() {
vector<int> t;
t.push_back(112);
cout << "t[1]:" <<" value="<< t[0] << " address=" << &t[1] << endl;
vector<int> m = move(t);
cout << "m[1]:" <<" value= "<< m[0] << " address=" << &m[1] << endl;
}
输出:
t[1]: value=112 address=0x6e1774
m[1]: value= 112 address=0x6e1774
可以看到move前后元素的内存地址并没有发生改变。
vector<int> func2() {
vector<int> t;
t.push_back(112);
cout << "t[1]:" << " value=" << t[0] << " address=" << &t[1] << endl;
return t; // 默认执行move
}
int main() {
vector<int> m = func2();
cout << "m[1]:" << " value= " << m[0] << " address=" << &m[1] << endl;
}
这里的内存地址m和t也是一样的。
虚析构函数
C++ 对象基类和子类都定义了析构函数,但不是虚析构函数。使用基类指针指向子类,用基类指针删除子类对象时,只会调用基类的析构函数。
C++ 对象基类和子类都定义了析构函数,都是虚析构函数。使用基类指针指向子类,用基类指针删除子类对象时,这种情况才会先调用子类的析构函数,再调用基类的析构函数。
Reference List
- https://www.geeksforgeeks.org/c-plus-plus/