const
class A {
private:
const int a;
public:
A() :a(0) {};
A(int x) :a(x) {};
int getValue();
int getValue() const;
};
void function()
{
A b;
const A a;
const A *p = &a;
const A &q = a;
char greeting[] = "Hello";
char *p1 = greeting;
const char* p2 = greeting;
char* const p3 = greeting;
const char* const p4 = greeting;
}
void function1(const int Var);
void function2(const char* Var);
void function3(char* const Var);
void function4(const int& Var);
const int function5();
const int* function6();
int* const function7();
没有const &a,引用只是对象的别名,不是对象,不能用const修饰
static
-
修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。 -
修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。 -
修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。 -
修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。
class Point
{
public:
void init()
{
}
static void output()
{
}
};
void main()
{
Point::init();
Point::output();
}
class Point
{
public:
void init()
{
}
static void output()
{
printf("%d\n", m_x);
}
private:
int m_x;
};
void main()
{
Point pt;
pt.output();
}
this指针
this指针是一个隐含在每一个非静态成员函数中的特殊指针,指向调用该成员函数的那个对象。 常需要显式使用this指针的情况:1.实现对象的链式引用;2.避免对同一对象进行赋值操作;3.实现一些数据结构,如 list 。
inline内联函数
不用进入函数,直接执行函数体,提高效率 类内定义的函数,除了虚函数,都默认为内联函数 相当于把内联函数体复制到内联函数调用点处,消耗内存空间
class A {
int doA() { return 0; }
};
class B {
int doB();
};
inline int B::doB() { return 0; }
虚函数的内联 虚函数可以是内联函数,但是当虚函数是多态时不能内联。因为多态在运行期执行,内敛在编译期执行,编译器不知道运行期调用哪个代码。 inline virtual 唯一可行的时候是:编译器知道所调用的对象是哪个类,这只有在编译器拥有实际对象而非对象的引用或指针时才会发生
class Base {
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived:public Base{
public:
inline void who()
{
cout << "I am Dervied\n";
}
};
int main() {
Base b;
b.who();
Base *ptr = new Derived();
ptr->who();
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
volatile
volatile是一种类型修改符,用它声明的类型表示可以被某些未知因素修改(操作系统,硬件,其他线程),编译期不应对这样的对象优化。 volatile关键字声明的变量,每次都要从内存中取值 const可以是volatile 指针可以是volatile
volatile int i = 0;
volatile char* vpch;
char* volatile pchv;
assert()
是一个宏,而非函数。 作用是:如果条件返回错误,则终止程序执行
#define NDEBUG
#include <cassert>
assert(p != NULL);
sizeof()
对数组使用,得到整个数组所占的空间大小 对指针使用,得到指针本身所占的空间大小
#pragma pack(n)
设定结构体,联合以及类成员变量以n字节对齐
#pragma pack(push)
#pragma pack(4)
struct test {
char m1;
double m4;
int m3;
};
#pragma pack(pop);
位域
类为非静态成员函数定义位域,占多少个二进制位,可节省一定内存 指针和取地址不可作用于位域
Bit mode 2;
extern C
被extern修饰的变量是extern类型的 被extern "C"修饰的变量表示用C语言处理当前代码 防止c++代码修改和c语言不匹配
#ifdef __cplusplus
extern "C"{
#endif
void *meset(void*,int,size_t);
#ifdef __cplusplus
}
#endif
struct和typedef struct
c中
typedef struct Student{
int age;
} S;
struct Student{
int age;
};
typedef struct Student S;
void Student(){}
c++中
struct Student{
int age;
};
void f1(Student me);
void f2(struct Student me);
typedef struct Student{
int age;
}S;
void Student(){}
void S(){}
int main(){
Student();
struct Student me;
return 0;
}
struct & class
struct适合看作数据结构的实现体,class适合看作对象的实现体 默认的访问权限:class是private,struct是public
union联合
一种节省空间的特殊的类,可以拥有很多数据结构,但在同一时刻只能有一个数据成员有值,其他数据成员变成未定义状态。 默认访问权限为public 可以包含构造函数和析构函数 不能含有引用类型的成员 不能继承自其他类,不能作为基类 不能含有虚函数 匿名union在定义所在的作用域可以直接访问union成员 匿名union不能包含protect,private成员 全局匿名union必须是static静态的
union UnionTest
{
UnionTest() :i(10) {};
int i;
double d;
};
static union {
int i;
double d;
};
int main()
{
UnionTest u;
union {
int i;
double d;
};
cout << u.i << endl;
::i = 20;
cout << ::i << endl;
i = 30;
cout << i << endl;
return 0;
}
c实现c++面向对象特性
封装:使用函数指针将属性和方法封装到结构体中 继承:结构体嵌套 多态:父类和子类的函数指针不同
expliit(显示)关键字
explicit修饰构造函数时,可以防止隐式转换和复制初始化 explicit修饰转换函数时,可以防止隐式转换,按语境转换除外
下列语境中,期待类型 bool,且若声明 bool t(e); 良构则进行隐式转换(即考虑如 explicit T::operator bool() const; 这样的隐式转换函数)。称这种表达式 e 按语境转换为 bool。 if、while、for 的控制表达式; 内建逻辑运算符 !、&& 和 || 的操作数; 条件运算符 ?: 的首个操作数; static_assert 声明中的谓词; noexcept 说明符中的表达式;
struct A {
A(int) {}
operator bool()const { return true; }
};
struct B {
explicit B(int) {}
explicit operator bool()const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main() {
A a1(1);
A a2 = 1;
A a3{ 1 };
A a4 = { 1 };
A a5 = (A)1;
doA(1);
if (a1);
bool a6(a1);
bool a7 = a1;
bool a8 = static_cast<bool>(a1);
B b1(1);
B b2 = 1;
B b3{ 1 };
B b4 = { 1 };
B b5 = (B)1;
doB(1);
if (b1);
bool b6(b1);
bool b7 = b1;
bool b8 = static_cast<bool>(b1);
return 0;
}
friend 友元类和友元函数
能访问private成员 破坏封装性 友元关系不可传递 友元关系是单向的 友元声明的形式和数量不受限制
class CCar
{
private:
int price;
friend class CDriver;
};
class CDriver
{
public:
CCar myCar;
void ModifyCar()
{
myCar.price += 1000;
}
};
class CCar;
class CDriver
{
public:
void ModifyCar(CCar* pCar);
};
class CCar
{
private:
int price;
friend int MostExpensiveCar(CCar cars[], int total);
friend void CDriver::ModifyCar(CCar* pCar);
};
void CDriver::ModifyCar(CCar* pCar)
{
pCar->price += 1000;
}
int MostExpensiveCar(CCar cars[], int total)
{
int tmpMax = -1;
for (int i = 0; i<total; ++i)
if (cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
using
- using声明
一条using声明一次只能引入命名空间的一个成员 using namespace_name::name - 构造函数的using声明
派生类可以重用其直接基类定义的构造函数,编译器生成每个构造函数对应的派生类构造函数
class Derived:Base{
public:
using Base::Base;
}
尽量少使用using指示,污染命名空间(导入的名字过多可能重名,发生覆盖)
using namespace std;
多使用using声明
int x;
std::cin>>x;
std::cout<<x<<std::endl;
or
using std::cin;
using std::cout;
using std::endl;
int x;
cin>>x;
cout<<x<<endl;
::范围解析运算符
- 全局作用域符
::name :用于类型名称(类,类成员,成员函数,变量等)前,表示作用域为全局命名空间 - 类作用域符
class::name :用于表示指定类型的作用域时某个具体的类 - 命名空间作用域符
namespace::name :用于表示指定类型的作用域时某个命名空间
int count = 11;
class A {
public:
static int count;
};
int A::count = 21;
void fun() {
int count = 31;
count = 32;
}
int main() {
::count = 12;
A::count = 22;
fun();
return 0;
}
enum枚举类型
- 限定作用域的枚举类型
enum class open_modes{input,output,append};
- 不限定作用域的枚举类型
enum color{red,yellow,green};
enum{floatPrec=6,double=10};
decltype
用于检查实体的声明类型或表达式的类型及值分类 decltype(expression)
template <typename It>
auto fcn(It beg, It end)->decltype(*beg)
{
return *beg;
}
template <typename It>
auto fcn2(It beg,It end)->typename remove_reference<decltype(*beg)>::type
{
return *beg;
}
#include <string>
using namespace std;
class Student{
public:
static int total;
string name;
int age;
float scores;
};
int Student::total = 0;
int main(){
int n = 0;
const int &r = n;
Student stu;
decltype(n) a = n;
decltype(r) b = n;
decltype(Student::total) c = 0;
decltype(stu.name) url = "abcdefg";
return 0;
}
引用
- 左值引用
常规引用,一般表示对象的身份 - 右值引用
右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面: 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。 能够更简洁明确地定义泛型函数。
- 引用折叠
X& &、X& &&、X&& & 可折叠成 X& X&& && 可折叠成 X&&
宏
#define xxx #define Max(x,y) ((x)>(y)?(x):(y)) 宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对 “参数” 进行的是一对一的替换。
成员初始化列表
效率高,少一次调用默认构造的过程.
有些场合必须要用初始化列表: 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化
initializer_list 列表初始化
#include <iostream>
#include <vector>
#include <initializer_list>
template<class T>
struct S {
std::vector<T> v;
S(std::initializer_list<T> l) :v(l) {
std::cout << "constructed with a" << l.size() << "-element list";
}
void append(std::initializer_list<T>l) {
v.insert(v.end(), l.begin(), l.end());
}
std::pair<const T*, std::size_t>c_arr()const {
return{ &v[0],v.size() };
}
};
template <typename T>
void templated_fn(T) {}
int main() {
S<int>s1 = { 1,2,3,4,5 };
S<int>s2{ 1,2,3,4,5 };
s1.append({ 6,7,8 });
std::cout << "The vector size now is" << s.c_arr().second << "ints:\n";
for (auto n : s.v) {
std::cout << n << ' ';
}
std::cout << "\n";
std::cout << "Range-for over brace-init-list: \n";
for (int x : {-1, -2, -3}) {
std::cout << x << ' ';
}
std::cout << '\n';
auto al = { 10,11,12 };
std::cout << "The list bound to auto has size() = " << al.size() << '\n';
templated_fn<std::initializer_list<int>>({ 1, 2, 3 });
templated_fn<std::vector<int>>({ 1, 2, 3 });
}
面向对象
封装
把客观的事物封装成抽象的类,类可以把自己的数据做出公开,隐藏或者对朋友公开. 关键字: public:可被任意实体访问 private:可被本类或者友元访问 protected:只能被本类和子类访问
继承
基类(父类)–>派生类(子类)
多态
以封装和继承为基础 分类及实现
- 重载多态:函数重载,运算符重载
- 子类型多态:虚函数
- 参数多态性:类模板,函数模板
- 强制多态:基本类型转换,自定义类型转换
静态多态
函数重载
class A
{
public:
void do(int a);
void do(int a,int b);
};
动态多态
虚函数:virtual修饰成员函数 动态绑定:当使用基类的引用或指针调用一个虚函数时发生动态绑定.
可以将派生类的对象赋值给基类的指针或引用,反之不可 普通函数(非类成员函数)不能是虚函数 静态函数(static)不能是虚函数 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针) 析构函数可以是虚函数,为了解决基类指针指向子类并删除的内存泄漏问题 内联函数不能是表现多态性时的虚函数
class Shape
{
public:
virtual double calcArea() {}
virtual ~Shape() {};
};
class Circle :public Shape
{
public:
virtual double calcArea() {};
};
class Rect :public Shape
{
public:
virtual double calcArea() {};
};
int main()
{
Circle *c1 = new Circle();
Shape* s1 = c1;
Shape* s2 = new Circle();
s2->calcArea();
delete c1;
c1 = nullptr;
delete s1;
s1 = nullptr;
delete s2;
s2 = nullptr;
Shape* S3 = new Shape();
}
纯虚函数
在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做 virtual int A()=0; 最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
虚函数、纯虚函数 类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖(override),这样的话,编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。 虚函数在子类里面可以不重写;但纯虚函数必须在子类实现才可以实例化子类。 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类完成。 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。 虚基类是虚继承中的基类
虚函数指针,虚函数表
虚函数表:在程序只读数据段,存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。 虚函数指针:在含有虚函数类的对象中,指向虚函数,在运行时确定。
虚继承
虚继承、虚函数 相同之处:
- 都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
不同之处:
- 虚继承
虚基类依旧存在继承类中,只占用存储空间 虚基类表存储的是虚基类相对直接继承类的偏移 - 虚函数
虚函数不占用存储空间 虚函数表存储的是虚函数地址
模板类、成员模板、虚函数
类模板中可以有虚函数 一个类的成员模板函数不能是虚函数
抽象类、接口类、聚合类
抽象类:含有纯虚函数的类 接口类:仅含有纯虚函数的抽象类 聚合类:用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点: 所有成员都是 public 没有定义任何构造函数 没有类内初始化 没有基类,也没有 virtual 函数
内存分配和管理
-
malloc、calloc、realloc、alloca malloc:申请指定字节数的内存。申请到的内存中的初始值不确定。 calloc:为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。 realloc:更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。 alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存。 -
malloc free 分配释放内存
char *str=(char*)malloc(100);
assert(str!=nullptr);
free(str);
str =nullptr;
- new delete
new/new[] 先调用malloc分配内存后调用构造函数 delete/delete[] 先调用析构函数后free释放空间
int main()
{
T* t= new T();
delete t;
return 0;
}
new可以自己计算所需要的空间来分配,malloc需要自己输入申请空间的字节数
- 定位new
向new传递地址参数,从而预先在指定的内存区域创建对象
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }
int main(){
char buffer[512];
int *p1 = new (buffer) int[10];
int *p2 = new int[10];
}
定义一个只能在堆上(栈上)生成对象的类
只能在堆上 方法:将析构函数设置为私有
只能在栈上 方法:将 new 和 delete 重载为私有
智能指针
c++11有三种智能指针
shared_ptr 实现共享式拥有概念。多个智能指针指向相同对象,该对象和其相关资源会在 “最后一个 reference 被销毁” 时被释放。
weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空。可打破环状引用,解决死锁问题
unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。一旦拥有着被销毁或编程 empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。它对于避免内存泄漏——如 new 后忘记 delete ——特别有用。unique_ptr 用于取代旧版本的 auto_ptr
强制类型转换运算符
-
static_cast 用于非多态类型的转换 不执行运行时类型检查(转换安全性不如 dynamic_cast) 通常用于转换数值数据类型(如 float -> int) 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法) 向上转换是一种隐式转换。 -
dynamic_cast 用于多态类型的转换 执行行运行时类型检查 只适用于指针或引用 对不明确的指针的转换将失败(返回 nullptr),但不引发异常 可以在整个类层次结构中移动指针,包括向上转换、向下转换 -
const_cast 用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 ) -
reinterpret_cast 用于位的简单重新解释 滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全) 也允许将任何整数类型转换为任何指针类型以及反向转换。 reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。 reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。 -
bad_cast 由于强制转换为引用类型失败,dynamic_cast 运算符引发 bad_cast 异常。 bad_cast 使用
try {
Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
}
catch (bad_cast b) {
cout << "Caught: " << b.what();
}
运行时类型信息 (RTTI)
typeid、type_info 使用
#include <iostream>
using std::cout;
class Flyable {
public:
virtual void takeoff() = 0;
virtual void land() = 0;
};
class Bird :public Flyable {
public:
void foraging() {}
virtual void takeoff() {}
virtual void land() {}
virtual ~Bird() {}
};
class Plane :public Flyable {
public:
void carry() {}
virtual void takeoff() {}
virtual void land() {}
};
class type_info {
public:
const char* name()const;
bool operator ==(const type_info & rhs)const;
bool operator !=(const type_info & rhs)const;
int before(const type_info & rhs)const;
virtual ~type_info() {};
};
void doSomething(Flyable *obj) {
obj->takeoff();
cout << typeid(*obj).name() << endl;
if (typeid(*obj)==typeid(Bird))
{
Bird *bird = dynamic_cast<Bird*>(obj);
bird->foraging();
}
obj->land();
}
int main() {
Bird *b = new Bird();
doSomething(b);
delete b;
b = nullptr;
return 0;
}
|