C++基础知识
2.1 结构体和类
在C语言中通过struct关键字定义结构体,C语言中的结构体可以包含多个类型任意的数据成员,但是不能包含函数成员。在C++中同样可以通过struct关键字定义结构体,与C语言不同的是该结构体支持定义成员函数。
struct point{
int x;
int y;
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;
pt.x = 0;
pt.y = 0;
pt.output();
return 0;
}
除此之外,C++还提供了class关键字用于定义类,其基本语法和定义结构体大抵类似:
class point{
public://默认是private,私有成员无法在类外访问
int x;
int y;
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;//实例化point类(创建point类的对象)
pt.x = 0;
pt.y = 0;
pt.output();
return 0;
}
二者的主要区别在于struct定义的类成员默认访问权限是public,而class定义的类成员默认访问权限是private。
2.2 构造函数和析构函数
构造函数用于初始化类成员,这种特殊的函数和类名相同,没有返回值。
class point{
public://默认是private,私有成员无法在类外访问
int x;
int y;
void point(){//定义构造函数
x = 0;
y = 0;
}
void output(){
cout<<x<<endl<<y<<endl;
}
};
int main(){
point pt;//实例化point类,会自动调用构造函数
pt.output();
return 0;
}
有些情况下,编译器会为没有定义构造函数的类创建默认构造函数。倘若手动创建了构造函数,无论该构造函数什么样,编译器都不会创建默认构造函数。
析构函数用于释放对象占据的内存空间,析构函数名字为在类名前面加上~,析构函数没有参数也没有返回值:
class String{
private:
char* p;
public:
String(int n){
p = new char[n];
}
//析构函数,对象消亡时自动调用该函数
~String(){
delete[] p;//构造函数中在堆内存中开辟了一段空间,如果不释放便会引起内存泄漏
}
}
2.3 函数重载
在C++中允许在同一作用域中出现同名函数,这种行为即函数重载,函数重载发生在同名函数具有不同数量或者不同类型的参数情形下:
class Point{
private:
int x;
int y;
public:
Point(){
x = 0;
y = 0;
}
//重载构造函数
Point(int x, int y){
this->x = x;
this->y = y;
}
}
仅仅返回值类型不同的两个函数不能构成函数重载。
2.4 类继承
类是可以继承的,通过继承可以让子类(派生类)重用基类(父类)的成员变量和函数。
从成员访问权限的角度来看,共有三种继承方法,他们分别是公有继承(public),私有继承(private),保护继承(protected),private继承时父类的全部成员在子类中都变为private,public继承时父类的全部成员在子类都维持原有访问级别,protected继承时基类的全部成员在子类都变为protected。值得注意的是,私有成员只能在当前类中访问,无法被继承。
#include<iostream>
using namespace std;
class Animal {
private:
const char *name;
public:
Animal(const char *name) {
this->name = name;
cout << "construct Animal" << endl;
}
~Animal() {
cout << "deconstruct Animal" << endl;
}
};
class Dog :public Animal {
public:
Dog(const char *name):Animal("Hello") {//调用基类构造函数,如果基类构造函数无参会自动调用
cout << "construct Dog" << endl;
}
~Dog() {
cout << "destruct Dog" << endl;
}
};
int main() {
Dog dog("dog1");
}
该例子展示了派生类如何调用基类的构造函数,并且说明实例化派生类时,基类构造函数先于派生类构造函数调用。清除派生类对象时,派生类析构函数先于基类析构函数被调用。
2.5 多态性
多态性是一种在运行时决定调用基类方法还是调用重写的派生类方法的技术,因此多态性的实现总是和成员函数覆盖关联在一起,注意函数覆盖和函数重载是完全不同的两个概念,重载指的是在相同作用域下定义多个名字相同的不同函数,而覆盖指得是派生类覆盖基类中定义的同名函数,要求两个函数包括函数名和参数列表在内的东西完全一致,且基类函数是虚函数。
如果子类函数和基类函数同名(不考虑参数列表),那么无论基类函数是否是虚函数,结果都是派生类函数隐藏了基类函数。
如果子类函数和基类函数完全相同,但基类函数非虚函数,这种情形也是隐藏。
隐藏是无法用来实现多态的。
#include<iostream>
using namespace std;
class Animal {
private:
const char *name;
public:
Animal(const char *name);
~Animal();
void talk();
};
Animal::Animal(const char *name) {
this->name = name;
cout << "construct Animal" << endl;
}
Animal::~Animal() {
cout << "deconstruct Animal" << endl;
}
void Animal::talk() {
cout << "an animal is talking..." << endl;
}
class Dog :public Animal {
public:
Dog(const char *name);
~Dog();
void talk();
};
Dog::Dog(const char *name) :Animal("Hello") {
cout << "construct Dog" << endl;
}
Dog::~Dog() {
cout << "destruct Dog" << endl;
}
void Dog::talk() {
cout << "dog is barking" << endl;
}
int main() {
Dog dog("dog1");
Animal *pAnimal;
pAnimal = &dog;
pAnimal->talk();
}
从内存空间上看,派生类是包含着继承的基类的,因此派生类地址可以不通过强制类型转换直接赋值给父类指针,转换以后编译器会把该指针所指向的对象当成是基类对象(忽略掉派生类附加的内存空间)。因此如果通过基类指针来调用一个非虚函数,编译期间就会绑定基类函数的地址(early binding),如果调用的是一个虚函数(virtual修饰),编译器会在运行时根据实际对象的类型来决定调用哪个函数(late binding)。
要实现多态,要做的就是给基类函数修饰上virtual,然后在派生类中重写该函数。
2.6 引用
引用就是一个变量的别名,引用类型变量一经创建就要初始化,初始化之后就和引用到的变量代表同一块内存,不能再做修改。
int a = 5;
int &b = a;
//从此之后b就是变量a的别名,访问b和访问a是一样的,他们具有相同的内存地址。
引用通常用于函数形参,通过引用传参可以实现函数内部直接修改实参的内存,同时还比指针传参具有更高的可读性。