C++兼容C语言,所以基础部分可以 参考C语言基础快查。
C++数据类型
C++比C的整型多了个布尔类型 布尔型 bool
, bool类型变量占用内存1字节。
C++ 中的类型限定符
限定符 | 说明 |
---|---|
const | const 类型的对象在程序执行期间不能被修改。 |
volatile | 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
存储类
mutable
mutable 能用来修饰类的数据成员,被 mutable 修饰的数据成员,可以在 const 成员函数中修改。比如const意思是“这个函数不修改对象内部状态”,mutable意思是“这个成员变量不算对象内部状态”,mutable告诉编译器修改它并不影响const语义,所以就不需要禁止const函数修改它。
thread_local
thread_local 声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
- thread_local 说明符可以与 static 或 extern 合并。
- thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
// 命名空间下的全局变量
thread_local int x;
class X
{
// 类的static成员变量
static thread_local std::string s;
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
// 本地变量
thread_local std::vector<int> v;
}
C++ 引用
C++比C多了引用类型,其实引用就是给变量的一个别名。
- 没有空引用,引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。
- 引用必须在创建时被初始化。
int a = 1;
int& ref = a;
那么ref
就是a
,a
也是ref
。
C++ 函数
C++ 函数在C的函数又增加了为参数指定默认值等特性。
参数默认值
定义函数时,可以为参数列表中后边的每一个参数指定默认值。当调用函数时,实参的值留空,形参就使用这个默认值。
int sum(int a, int b= 1);
int sum(int a = 0, int b = 1);
int sum(int a =0, int b);
这样不行
引用作为参数
引用作为参数,函数内部实际上使用的是实参,不会引起形参的拷贝。
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
引用作为返回值
static int sum = 0;
int& swap(int& x, int& y)
{
sum = x + y;
return sum;
}
C++ 类 & 对象
面向对象的基本特征:封装、继承和多态。
C++ 类定义
class People
{
public:
std::string name;
double age;
};
定义 C++ 对象
People people;
数据成员访问
people.name = "XiaoMing";
类成员函数
类成员函数声明:
class People
{
public:
std::string name;
double age;
void run(void);
};
类成员函数定义:直接在类里面定义
class People
{
public:
std::string name;
double age;
void run(void){
std::out<<"run"<<std::endl;
}
};
类成员函数定义:在类外定义
类外定义需要使用范围解析运算符::
, ::的前面是类名,::后面是函数名
void People::run(void){
std::out<<"run"<<std::endl;
}
类成员函数调用:
People people;
people.run();
类访问修饰符
在修饰数据成员或者成员函数时:
修饰符 | 说明 |
---|---|
public | 公有成员:类内外均可访问 |
private | 私有成员:只有本类或者友元类/函数可以访问 |
protected | 保护成员:本类、友元类/函数、派生类是可访问的 |
class内默认访问权限是private,struct默认访问权限是public。
构造函数
创建新对象时被自动调用执行,函数名与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void,可以带参数。
如果一个类没有定义任何构造函数,编译器会创建合成的默认构造函数。
构造函数作用:为某些成员变量设置初始值。
class People
{
public:
People();//<--构造函数
std::string name;
double age;
void run(void);
};
People people;
带参数的构造函数。
class People
{
public:
People(std::string name);//<--带参数的构造函数
std::string name;
double age;
void run(void);
};
People people("XiaoMing");
如果想在自定义其他构造函数的同时保留默认的构造函数。在类定义添加默认构造函数声明 = default
即可。
People() = default;
初始化列表
class People
{
public:
People(std::string name):name(name);//<--初始化列表
std::string name;
double age;
void run(void);
};
People people("XiaoMing");
析构函数
对象被销毁时自动调用。名称与类的名称是完全相同的,需要前面加了个波浪号(~)作为前缀,不返回任何值,也不带任何参数。
主要用来用来在对象销毁前释放对象申请的资源。
class People
{
public:
People();//<--构造函数
~People();
std::string name;
double age;
void run(void);
};
People people;
拷贝构造函数
创建对象可以使用一个存在的对象初始化新对象。
- 如果类中没有定义拷贝构造函数,编译器会自行定义一个。
- 如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
class People
{
public:
People();//<--构造函数
People(const People& aPeople){
}
~People();
std::string name;
double age;
void run(void);
};
People people;
People other = People(people);
友元函数
类的友元函数是定义在类外部,声明在类的内部,表示这个外部函数可以访问类的保护和私有成员。
友元函数不是成员函数。
class People
{
public:
People();
friend void sayHello( People &people );
~People();
std::string name;
double age;
void run(void);
};
this 指针
对象使用 this 指针来访问自己的地址,自有成员函数才有 this 指针。
class People
{
public:
People();
~People();
std::string name;
double age;
void setName(std::string name){
this->name = name;
}
};
指向类的指针
指向类的指针和指向结构体的指针一致。可以使用成员访问运算符 ->来访问成员,或者解引用*
的方式引用指针。
People tom = People();
People *ptr = &tom;
std::cout>>ptr->name>>std::endl;
std::cout>>(*ptr).name>>std::endl;
类的静态成员
static 把类成员定义为静态成员。
- 包括静态成员变量和静态成员函数。
- 静态成员变量只存在一份,普通成员和静态成员函数都可访问静态成员变量。
- 静态成员函数可以访问静态成员变量,不能直接访问类普通成员,没有this指针。
// People.h
class People
{
public:
People();
~People();
std::string name;
double age;
static std::string className;
static std::string &getClassName(){
return className;
}
};
静态成员变量的初始化在类外面进行
//People.cpp
std::string People::className = "People";
外部使用位运算符::
访问
std::cout<<People::className<<std::end;
std::cout<<People::getClassName()<<std::end;
继承
使用已有的类定义新的类,新的类含有原来类的成员的同时,可以创建新的新的成员。
已有的类称为基类,新建的类称为派生类。
继承的格式
class 派生类: 访问控制符 基类
class People
{
public:
People();
~People();
std::string name;
double age;
void setName(std::string name){
this->name = name;
}
};
class Chinese: public People{
public:
std::string id_number;
}
继承访问控制符
继承不写访问控制符:
- class 的默认继承方式是: private
- struct 的默认继承方式是:public
派生类继承了所有的基类方法,下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
public继承:(最大public)
public成员是pulic的,protected成员是protected的,private成员无法继承。
protected继承:(最大protected)
public成员是protected的,protected成员是protected的,private成
员无法继承。
private继承:(最大private)
public成员是private的,protected成员是private的,private成员无法继承。
多继承
多继承格式:
class 派生类: 访问控制符 基类1, 访问控制符 基类2, 访问控制符 基类3, ...
多个基类中含有同名函数,会产生二义性,在访问时可以加上域控制符:
#include <iostream>
using namespace std;
class classA{
public:
void func (int aInt){
cout<<"classA::func"<<aInt<<endl;
}
void func (char aChar){
cout<<"classA::func"<<aChar<<endl;
}
};
class classB{
public:
void func (double aDouble){
cout<<"classB::func"<<aDouble<<endl;
}
};
class classC: public classA, public classB{
public:
using classA::func;
using classB::func;
};
int main(int argc, const char * argv[]) {
classC obj = classC();
obj.func('a'); // 这里无法编译通过
obj.classA::func('a');
obj.classA::func(1.1);
return 0;
}
或者在派生类中通过using声明,把基类中的同名函数都引入到一个公共域中,这样重载解析规则就可以正常使用。
class classC: public classA, public classB{
public:
using classA::func; //!!!!
using classB::func;
};
int main(int argc, const char * argv[]) {
classC obj = classC();
obj.func('a'); // 这里就可以了
return 0;
}
但是如果基类包含一模一样函数使用using是无法编译通过的。
多态
通过继承关系为不同的类的实体提供统一的接口。使用接口调用时,会根据调用函数的对象的类型来执行不同的函数。
C++通过关键字virtual
实现继承的多态。
虚函数 :在基类中使用 virtual 声明的函数。派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数;
class People{
public:
virtual void sayHello(){
cout<<"I am people"<<endl;
}
};
class Student: public People{
public:
void sayHello(){
cout<<"I am Studenmt"<<endl;
}
};
class Teacher: public People{
public:
void sayHello(){
cout<<"I am Teacher"<<endl;
}
};
int main(int argc, const char * argv[]) {
People *ptr = NULL;
Student student;
Teacher teacher;
ptr = &student;
ptr->sayHello();
ptr = &teacher;
ptr->sayHello();
return 0;
}
运行结果如下:
I am Studenmt
I am Teacher
如果基类的sayHello不使用virtual修饰,那么main函数中的指针ptr时静态绑定的,指向派生类对象的基类的方法。
比如把sayHello的virtual去掉:
class People{
public:
void sayHello(){
cout<<"I am people"<<endl;
}
};
结果如下,都是运行的基类的方法:
I am people
I am people
如果在基类中定义虚函数,但不想在基类中实现该函数,就可以使用纯虚函数,如果。
virtual void sayHello() = 0;
含有纯虚函数的类叫做抽象类,如果它的派生类不实现基类中的纯虚函数,那么这个派生类也是抽象类。抽象类不能定义对象。
重载、重写(覆盖)、隐藏
重载 overload
同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。
- 函数返回值类型与构成重载无任何关系。
- 类的静态成员函数与普通成员函数可以形成重载。
- 函数重载发生在同一作用域,如类成员函数之间的重载、全局函数之间的重载。