前言:
鄙人1年 Java 学习经历,对 Java 的掌握比较熟练,其实在 Java 之前学过 C#,比较喜欢这两种语言。听闻 C++ 难学,精通C++ 21年 之类的传言,因此当年选课的时候毫不犹豫选了 Java。后来一想,一个 CS 学生只会写 CRUD 代码,不懂 C++ 好像有点说不过去,前段时间也在知乎上看到 “ 不管用什么语言,最后都会C++真香 ” 的言论。百闻不如一见,自己不试试看是不知道的,于是我就抱着试试看的心态,花了半天时间做了点 C++ 的实验,掌握了 C++ 的基本特性。
结论:
C++ 其实没有我想的那么难,用起来很 灵活 是我对它的最大好感,C++ 确实真香。
实验总结:
C++ 的 OOP 系统编写时,头文件写类定义,cpp文件写方法实现 是一种通用办法。
防止多次编译的办法有两个:
// Code here
xxxxxxxxxx
编译和链接时,如果缺少 方法实现 的 cpp 是不行的,会报错 undefined reference to xxx
方法:
编写 makefile,声明依赖关系
xCC = g++ # 编译器
DEPENDENCIES = $(wildcard ./*.cpp) # wildcard 可理解为声明了满足后面条件的所有内容
Test.exe: $(DEPENDENCIES) # Test.exe 依赖所有 dependencies
g++ $(DEPENDENCIES) -g # 多cpp编译,-g 生成调试信息
# 隐含规则, .h .o 等文件将自动被检测到
clean: # make clean 命令即可启动
del *.exe; # 执行windows下删除指令
echo "[INFO] Delete OK! "
run:
a.exe
使用时
xxxxxxxxxx
make
make run
make clean
使用 vscode 时,预先定义好 launch 和 task 内部内容。编写方法网上有很多参考,这里不多说了。
C++ 的类编写:
xxxxxxxxxx
class A {
public:
A(); // 构造函数
~A(); // 析构函数
//other definition
protected:
//definition here
private:
//definition here
}
C++ 的多继承:
xxxxxxxxxx
class A : public B, public C {
}
其中,冒号表示继承,继承的类前面的修饰符 一般用 public ,这样的话它工作起来和大部分 OO 语言一样。如果用 protected,则基类 public/protected 内容在子类中表现为 protected,private 同理。
虚函数 和 纯虚函数
虚函数拥有父类的缺省实现,但可以自定义一种基类实现。如果子类中不重写,则它默认是父类的缺省实现。
override
关键词表示这个方法重载父类的虚方法,如果重载失败,将通不过静态代码检查。
虚方法是为了实现函数调用的 多态性,如下例子所示:
xxxxxxxxxx
using namespace std;
class Test {
public:
virtual void printArea() {
cout << "Base printArea()" << endl;
}
void printArea2() {
cout << "Base printArea()" << endl;
}
};
class Child : public Test {
public:
virtual void printArea() override {
cout << "Child printArea()" << endl;
}
void printArea2() {
cout << "Child printArea()" << endl;
}
};
int main(int argc, char** args, char** argv) {
Test* test = new Child();
test->printArea(); // 是虚函数的重载,调用了子类的方法
test->printArea2(); // 不是虚函数,调用了父类的方法
}
纯虚函数相当于 接口,必须在子类中被重写,否则子类是 abstract 类,不能实例化。
xxxxxxxxxx
virtual void printArea() = 0; //加上 =0 表明这是纯虚函数
友元函数
友元函数虽然定义在类中,但不是成员函数,友元函数能访问类的 protected 和 private 成员。
xxxxxxxxxx
class Test {
public:
friend int getProp1(Test* test);
protected:
int prop1 = 233;
};
int getProp1(Test* test) {
return test->prop1;
}
C++ 模板编程
模板用于定义泛型类和泛型方法:
xxxxxxxxxx
template <typename T> class List {
public:
List();
bool add(T element);
void printList();
private:
int maxSize;
int currentSize;
T* collection;
};
// implementation
template<typename T> List<T>::List() {
this->maxSize = INIT_LENGTH;
this->currentSize = 0;
this->collection = (T*)malloc(sizeof(T)*this->maxSize);
}
template<typename T> bool List<T>::add(T element) {
if(this->currentSize + 1 >= this->maxSize) {
// Expand
this->maxSize += EXPAND_LENGTH;
this->collection = (T*)realloc(this->collection, sizeof(T)*this->maxSize);
cout << "Expand" << endl;
}
collection[this->currentSize++] = element;
return true;
}
template<typename T> void List<T>::printList() {
for(int i=0; i<this->currentSize; i++) {
cout << collection[i] << endl;
}
}
关键词 typename
和 class
是等价的。
模板编程时,全部写入 header 文件能支持所有泛型,若用 cpp 分开写方法实现,必须写明会用到的泛型,否则会报找不到相关泛型方法的错误。
inline 函数
用 inline 修饰在方法前面表示这个方法是内联方法,inline方法不会被直接装到栈里面,而是在编译时用其中的内容替换掉出现的地方,防止方法反复调用占据太多内存。
inline 方法必须是较为简单的方法,不能递归,不能出现复杂的流程控制语句。
inline 方法更多的是一种给编译器的建议,或者说是一种优化手段。
xxxxxxxxxx
inline int max (int a, int b) {
return a>b?a:b;
}
namespace 命名空间
命名空间就像一个装各种内容的容器,可以在里面定义任何东西,在调用时采用 namespace::xxxx
的方法进行调用。
命名空间可以避免同名函数在调用时的歧义。
xxxxxxxxxx
namespace space1 {
int add(int a, int b) {
return a + b;
}
typedef struct Tuple {
int a;
int b;
} Tuple;
}
namespace space2 {
int add(std::string a, std::string b) {
return a + b;
}
class Test {
// code here
}
}
异常处理
通过 throw 关键词抛出异常,异常可以是任何对象。
使用 try - catch 关键词捕获异常,catch 的异常应当符合抛出异常的类型。
xxxxxxxxxx
class DivisionByZeroException {
public:
DivisionByZeroException(string message);
std::string message;
};
DivisionByZeroException::DivisionByZeroException(std::string message) {
this->message = message;
}
xxxxxxxxxx
int divide(int a, int b) {
if (b == 0) {
throw new DivisionByZeroException("Div by 0 !");
}
return a/b;
}
void testException() {
try {
throw "Exception Occured!";
} catch (const char* e) {
cout << e << endl;
}
try {
divide(1, 0);
} catch (DivisionByZeroException* exception) {
cout << exception->getMessage() << endl;
}
}
// Console:
// Exception Ocuured!
// Div by 0 !
关于对象创建的一些事情
xxxxxxxxxx
Circle* circle = (Circle*)malloc(sizeof(Circle));
Circle* circle2 = new Circle();
Circle circle3;
使用 malloc 分配一个对象,对象内存分配给 heap,且不触发构造方法。
使用 new 创建一个对象,对象分配给 free memory,触发构造方法。
上面代码中的第三种方式也会触发构造方法。
运算符重载
改变运算符的定义,例如加法运算符作用在两个 Box 对象中间时,解释为返回一个新的 Box 对象,其属性为操作数 Box 的属性和。
xxxxxxxxxx
Circle Circle::operator+(Circle &circle) {
Circle newCircle;
newCircle.radius = this->radius + circle.radius;
return newCircle;
}
调用时应注意指针、引用等的转化。
指针和引用的几个区别