参考教程:link
基础不太好可以看的C++基础
文章目录
C++基础
1.虚函数与抽象类
- 纯虚函数
纯虚函数没有接口的实现范本,继承其的派生类必须实现
class A {
virtual int func() = 0;
};
- 虚函数
虚函数提供了接口范本,派生类可实现可不实现
class Base{
int b;
virtual void func(int a){
b = a;
}
};
class Derived : Base{
int a;
------
void func(int b){}
---or---
//空
};
- 父类指针引用子类内容
父类虚,则父类指针指向子类对象时,用子类对象的实现,父类不虚,则该指针用父类的实现
#include<iostream>
using namespace std;
class _ {
public:
void f(){
cout<<"b"<<endl;
}
};
class __ : public _{
public:
void f(){
cout<<"D"<<endl;
}
};
class _V :public _ {
public:
virtual void f(){
cout<<"D"<<endl;
}
};
class V {
public:
virtual void f(){
cout<<"b"<<endl;
}
};
class V_ :public V{
public:
void f(){
cout<<"D"<<endl;
}
};
class VV:public V{
public:
virtual void f(){
cout<<"D"<<endl;
}
};
int main()
{
_* p_;
V* pv;
_ _;
__ __;
_V _v;
V v;
V_ v_;
VV vv;
cout<<"-----------base-pointer--------"<<endl;
p_=&__;
cout<<"__ ";
p_->f();
p_=&_v;
cout<<"_v ";
p_->f();
pv=&v_;
cout<<"v_ ";
pv->f();
pv=&vv;
cout<<"vv ";
pv->f();
cout<<"-------------direct------------";
cout<<endl<<"__. ";
__.f();
cout<<"_v. ";
_v.f();
cout<<"v_. ";
v_.f();
cout<<"vv. ";
vv.f();
}
输出结果:
-----------base-pointer--------
__ b
_v b
v_ D
vv D
---------class-dot------------
__. D
_v. D
v_. D
vv. D
规律总结:
base-pointer
pbd | f() |
---|---|
_x | b |
vx | D |
class-dot
bd | f() |
---|---|
xx | D |
- 抽象类
包含虚函数的类
class A {
int a;
virtual void fun(void){}
}
- 同名隐藏
如果基类成员和派生类成员重名则基类被隐藏,允许隐藏存在不报错且不用虚函实现是为了在这个分支的继承链条中更改基类模板,但不更改其他链条的基类模板
#include<iostream>
using namespace std;
class Base{
public:
static void func(void){
cout<<"Base"<<endl;
}
};
class chainA_1:public Base{
public:
void func(void){
cout<<"chainA_1"<<endl;
}
};
class chainA_2:public chainA_1{
public:
void callBackBase(){
Base::func();
}
};
class chainB_1:public Base{
};
int main(void)
{
chainA_2 cA2;
chainB_1 cB1;
cA2.func();
cB1.func();
((Base)cA2).func();
Base::func();
cA2.callBackBase();
};
输出结果:
chainA_1
Base
Base
Base
Base
2.断言
- 断言
通过断言来保证被断言的量满足表达式的约束否则执行失败,并借助编译器输出行号便于诊断
#include <iostream>
#include <assert.h>
int main(){
int x = 7;
x = 9;
printf("hello\n");
assert(x==7);
return 0;
}
- 输出
hello
a.out: assert.cpp:7: int main(): Assertion `x==7' failed.
已中止 (核心已转储)
- 忽略断言
加入以下宏定义将使得断言失效
#define NDEBUG
3.位域
- 概念
使用位域来访问某个变量的某几个连续的比特位 - 约束
其内存布局在不同架构的机器上不同不可移植
其类型必须是整形或枚举类型 - 自动机制
超过整形长度的位域会被顺延滑动到下一个新建的整形开头
(int 型的整形滑动后会和char型的整形合并,如果char型不够位继续顺延滑动)
此时结构体长度增长
使用 ': 0’与下一个整形对齐用作本整形位域的收尾 (不用数距离下一个整形有多少个位了)
#include<iostream>
struct box{
unsigned int a: 1;
unsigned int : 2;
unsigned int b: 4;
unsigned int : 0;
unsigned int c: 8;
}boox;
struct b2{
unsigned int a: 31;
unsigned int b: 6;
unsigned short c:3;
}bb2;
struct b3{
unsigned int a: 31;
unsigned int b: 6;
unsigned char c:2;
}bb3;
struct b4{
unsigned int a: 31;
unsigned int b: 6;
unsigned char c:3;
}bb4;
unsigned short s;
unsigned int i;
unsigned long l;
unsigned char c;
double d;
float f;
int main()
{
printf("%d\t", boox.a=1);
printf("%d\t",boox.b=1);
printf("%d\t",boox.c=3);
printf("%d\t",bb2.a=1);
printf("%d\t",bb2.b=1);
printf("%d\t",bb2.c=3);
printf("%d\t",bb3.a=1);
printf("%d\t",bb3.b=1);
printf("%d\t",bb3.c=3);
printf("%d\t",bb4.a=1);
printf("%d\t",bb4.b=1);
printf("%d\n",bb4.c=3);
}
GDB观察内存
(gdb) b 46
...
(gdb) run
...
(gdb) n
...
1 1 3 1 1 3 1 1 3 1 1 3
(gdb) x /8bx &boox
0x555555558018 <boox>: 0x09 0x00 0x00 0x00 0x03 0x00 0x00 0x00
(gdb) x /8bx &bb2
0x555555558020 <bb2>: 0x01 0x00 0x00 0x00 0xc1 0x00 0x00 0x00
(gdb) x /8bx &bb3
0x555555558028 <bb3>: 0x01 0x00 0x00 0x00 0xc1 0x00 0x00 0x00
(gdb) x /8bx &bb4
0x555555558030 <bb4>: 0x01 0x00 0x00 0x00 0x01 0x03 0x00 0x00
- 初始化
与结构体成员初始化无异
struct stuff s1= {20,8,6};
struct stuff s1;
s1.field1 = 20;
s1.field2 = 8;
s1.field3 = 4;
- 归零
用与结构体同等大小的指针归零
int* p = (int *) &b1; // 将 "位域结构体的地址" 映射至 "整形(int*) 的地址"
*p = 0; // 清除 s1,将各成员归零
用同等大小的联合体成员归零
union u_box {
struct box st_box;
unsigned int ui_box;
};
union u_box u;
u.ui_box = 0;
4.面向对象
- 封装
class - 继承
Derived : base{
} - 多态
运算符重载
函数重载
虚函数重写
继承函数覆盖
5.enum类型
- enum是一个类型
- 结构体指定作用域
struct Color1 {
enum Type { RED = 102, YELLOW, BLUE };
};
//用法:
Color1 c1;
cout << c1.RED << endl;
- enum class 指定作用域(C++11)
enum class Color2 { RED = 2, YELLOW, BLUE };
//用法
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl;
cout << c2 << endl;// err
- 名称空间指定作用域
namespace Color {
enum Type { RED = 15, YELLOW, BLUE };
};
//用法
char c3 = static_cast<char>(Color3::RED);
char c4 = Color3::RED;//error: cannot convert ‘Color3’ to ‘char’ in initialization
cout << c3 << endl;
6.decltype
- 返回表达式的做右值引用或类型
int arr[1] = {1};
decltype(++i) //int& 左值
decltype(std::move(++i)) //int 右值
decltype(i++); //int 右值
int *ptr = arr;//指向第一个元素
decltype(*ptr)var10 = i;//int 操作返回左值
7.初始化列表
- 给成员变量赋初值
class person()
{
public:
Person(int a,int b,int c):m_A(a), m_B(b),m_C(c) {
}
private:
int m_A;
int m_B;
int m_C;
}
8.友元
- 全局函数作友元
- 类作友元
- 类的成员函数作友元
include<iostream>
using namespace std;
class Home;
class goodGay2{
public:
goodGay2();
void visit1(); // 不能访问 Home private
void visit2(); // 能访问 Home private
private:
Home * home;
};
class goodGay{
public:
goodGay();
void visit();
private:
Home * home;
};
class Home{
friend class goodGay; //类作友元
friend void goodGay2::visit2(); //类成员作友元
friend void GoodGay(Home& h); //全局函数作友元
public:
Home();
protected:
string livingroom;
private:
string bedroom;
};
Home::Home(){
livingroom = "liverrom";
bedroom = "bedroom";
}
goodGay::goodGay(){
this->home = new Home();
}
goodGay2::goodGay2(){
this->home = new Home();
}
void GoodGay(Home& h){
cout<<"-------GoodGayGFun--------"<<endl;
cout<<h.livingroom<<endl;
cout<<h.bedroom<<endl;
};
void goodGay::visit(){
cout<<"----------ClassGay-visit-----------"<<endl;
cout<<this->home->livingroom<<endl;
cout<<this->home->bedroom<<endl;
}
void goodGay2::visit1(){
cout<<"----------classGay2-visit-----------"<<endl;
//cout<<this->home->livingroom<<endl;//err protect
//cout<<this->home->bedroom<<endl; //err private
}
void goodGay2::visit2(){
cout<<"----------classGay2-visit2-----------"<<endl;
cout<<this->home->livingroom<<endl;
cout<<this->home->bedroom<<endl;
}
int main()
{
Home h;
GoodGay(h);
goodGay gay;
gay.visit();
goodGay2 gay2;
//gay2.visit();//err
gay2.visit2();
}
- 输出
-------GoodGayGFun--------
liverrom
bedroom
----------ClassGay-visit-----------
liverrom
bedroom
----------classGay2-visit2-----------
liverrom
bedroom
9.new关键字
- new关键字在堆区创建对象,需要手动释放
person p1 = new person(160);//堆区
delete p1;//手动释放
10.深浅拷贝
- 浅拷贝使用举例:
person p1(18,160); //栈区
person p2(p1); //栈区
- 问题:调用析构函数时,浅拷贝堆区的内存重复释放
height被析构delete两次
class person{
person(int age,int height){
this->age = age;
this->height = new int(height);
}
int age;
int* height;
~person(){
delete(height);//析构时被调用两次
height = NULL;//防止野指针出现
}
};
- 根因:
浅拷贝时,编译器默认将指针重复拷贝,指向同一块堆区,析构时delete同一块堆区两次出错
height= p.height; //对象p:this->height = new int(height);
- 解决:
创建深拷贝函数
person(const person& p1){
this->age = age;
this->height = new int(*p1.height);//拷贝时创建新的堆区给指针成员
}
11.explicit
被explicit修饰的函数只能被显示语境调用。
- 显隐式调用语境举例
初始化环境
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) {}
转换函数显式调用语境
if (b1) //OK:B::B(int)
bool b6(b1); //OK:B::operator Bool () const
构造函数显式调用语境
B b1(1); // OK:B::B(int)
B b3{1}; // OK:B::B(int)
Static_cast混合显示调用语境
bool b8 = static_cast<bool>(b1); // OK:B::operator Bool () const
转换函数隐式调用语境
A a1(1); //OK A::A(int)
bool a7 = a1; //OK A::operator bool()
构造函数隐式调用语境
B b1(1);
B b2 = 1; // 错误 explicit B(int)
B b4 = { 1 }; // 错误 explicit B(int)
bool b7 = b1; // 错误 explicit operator bool()
doB(1); // 错误 1->(B b) expliciit B(int)
12.函数指针
- 调用法
void (*pfunc)(int);
void func(int a){
cout<< a <<endl;
}
pfunc = func;
pfunc(2);
(*pfunc)();
13.goto
- 跳过一段代码转到 label 处继续执行,顺序执行到label以后的代码仍然执行
#include<iostream>
using namespace std;
int main()
{
bool error = false;
if(error)
goto END;
cout<<"not"<<endl;
END:
cout<<"END"<<endl;
}
输出:
not
end
14.do{}while(0)的作用
- 代替goto来跳过一段代码,方便阅读
#include<iostream>
using namespace std;
int main()
{
bool error = true;
do{
if(error)
break;
cout<<"not"<<endl;
}while(0);
cout<<"END"<<endl;
}
输出:
end
- 排除重命名干扰,使定义逻辑块更自由,内外数据通过非重名变量传递
#include<iostream>
using namespace std;
int main()
{
int k = 10;
int k2 = 8;
cout<<"k:";
cout<<k<<endl;
cout<<"k2:";
cout<<k2<<endl;
do{
k2 = 6;
int k = 100;
cout<<"k:";
cout<<k<<endl;
cout<<"k2:";
cout<<k2<<endl;
}while(0);
cout<<"k:";
cout<<k<<endl;
cout<<"k2:";
cout<<k2<<endl;
}
输出
k:10
k2:8
k:100
k2:6
k:10
k2:6
15.宏中的 #s 和 ##
- #s
用于将宏参转字符串
#include<iostream>
#include<string>
#define exp(s) #s
#define exp2(s) printf("test %s\n",gc_ ## s)
using namespace std;
int main()
{
const char* gc_s="so";
string str = exp( a b c d );
printf("macro string %s\n",exp(abcd));
exp2(s);
cout<<str<<endl;
return 0;
}
- 预编译
# 5 "well_number.cpp"
using namespace std;
int main()
{
const char* gc_s="so";
string str = "a b c d";
printf("macro string %s\n","abcd");
printf("test %s\n",gc_s);
cout<<str<<endl;
return 0;
}
- 输出
macro string abcd
test so
a b c d
16.extern “C”
-
编译时,允许以C符号表命名法查询C函数所在位置
-
nm命令查看符号表
~/Code/c++$ nm -C add.o
0000000000000000 T add
~/Code/c++$ nm -C addcpp.o
0000000000000000 T add(int, int)
~/Code/c++$ nm -C a.out | grep add
0000000000001236 T add
17.c和c++结构体
C++的结构体和类一样用
18. volatile关键字
- 告诉编译器被其修饰的变量在程序中的每一步操作都是有意义的,不要优化省略步骤
- 编译器无法感知到操作系统,硬件,其他线程会在对一个变量的连续重复操作步骤中更改变量,从而省略看起来重复无意义的步骤
- 直接寻址而非用缓存
- const可以是volatile的 例如 cpsr寄存器
- 指针也可以是volatile的 例如 ISR中修改指针(硬件)