C++ _01基础知识
程序编译过程
编译预处理:处理以 # 开头的指令;
编译、优化:将源码 .cpp 文件翻译成 .s 汇编代码;
汇编:将汇编代码 .s 翻译成机器指令 .o 文件;
链接:汇编程序生成的目标文件,即 .o 文件,并不会立即执行,因为可能会出现.cpp 文件中的函数引用了另一个 .cpp 文件中定义的符号或者调用了某个库文件中的函数。链接的目的就是将这些文件对应的目标文件连接成一个整体,从而生成可执行的程序 .exe 文件。
链接分为两种:
静态链接:代码从其所在的静态链接库中拷贝到最终的可执行程序中,在该程序被执行时,这些代码会被装入到该进程的虚拟地址空间中。
动态链接:代码被放到动态链接库或共享对象的某个目标文件中,链接程序只是在最终的可执行程序中记录了共享对象的名字等一些信息。在程序执行时,动态链接库的全部内容会被映射到运行时相应进行的虚拟地址的空间。
C++内存分区
代码区(.text 段):存放代码,不允许修改,但可以执行。编译后的二进制文件存放在这里。
栈:存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。
堆:动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。
全局区/静态存储区(.bss 段和 .data 段):存放全局变量和静态变量,程序运行结束操作系统自动释放,在 C 语言中,未初始化的放在 .bss 段中,初始化的放在 .data 段中,C++ 中不再区分了。
常量存储区(.data 段):存放的是常量,不允许修改,程序运行结束自动释放。
static使用详解
大佬对static用法的详解:guotianqing的博客
static修饰的变量和函数的只在定义的文件内可以使用。
静态局部变量
函数体结束后变量仍然存在。
静态全局变量和静态函数
只在定义该变量的文件内可以使用。
在类中的使用:静态数据成员和静态成员函数
类定义的静态数据成员和成员函数,不会随着实例化拷贝多个变量和函数。
单例模式中需要使用静态变量。单例模式详解
volatile关键字
被 volatile 修饰的变量,在对其进行读写操作时,会引发一些可观测的副作用。而这些可观测的副作用,是由程序之外的因素决定的。
智能指针
大佬对智能指针的详解:melodybinbin的博客
#include < memory>
共享指针(shared_ptr):资源可以被多个指针共享,使用计数机制表明资源被几个指针共享。通过 use_count() 查看资源的所有者的个数,可以通过 unique_ptr、weak_ptr 来构造,调用 release() 释放资源的所有权,计数减一,当计数减为 0 时,会自动释放内存空间,从而避免了内存泄漏。
独占指针(unique_ptr):独享所有权的智能指针,资源只能被一个指针占有,该指针不能拷贝构造和赋值。但可以进行移动构造和移动赋值构造(调用 move() 函数),即一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,可以通过该方法进行赋值。
弱指针(weak_ptr):指向 share_ptr 指向的对象,能够解决由shared_ptr带来的循环引用问题。
函数指针
#include<iostream>
using namespace std;
/**
* @brief 定义了一个变量pFun,这个变量是个指针,指向返回值和参数都是空的函数的指针!
*/
void (*pFun)(int);
/**
* @brief 代表一种新类型,不是变量!所以与上述的pFun不一样!
*/
typedef void (*func)(void);
void myfunc(void)
{
cout<<"asda"<<endl;
}
void glFun(int a){ cout<<a<<endl;}
int main(){
func pfun = myfunc;/*赋值*/
pfun();/*调用*/
pFun = glFun;
(*pFun)(2);
}
虚函数
虚函数的默认参数
虚函数是动态绑定的,默认参数是静态绑定的。默认参数的使用需要看指针或者应用本身的类型,而不是对象的类型。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun ( int x = 10 )
{
cout << "Base::fun(), x = " << x << endl;
}
};
class Derived : public Base
{
public:
virtual void fun ( int x=20 )
{
cout << "Derived::fun(), x = " << x << endl;
}
};
int main()
{
Derived d1;
Base *bp = &d1;
bp->fun(); // 10
return 0;
}
基类子类访问权限
基类类型只能通过虚函数访问子类的函数,不能子类的其他函数。子类可以通过“基类::对象”的方式访基类的成员和函数。
#include<iostream>
using namespace std;
class base {
public:
int num;
base() : num(0) {
cout << num << ' ';
cout << "Constructing base \n";
}
virtual void show() {
cout << "showBase\n";
}
};
class derived : public base {
public:
int num;
derived() : num(10) {
cout << num << ' ';
cout << "Constructing derived \n";
}
virtual void show() {
cout << "showDerivred\n";
}
};
int main(void){
derived* d = new derived();
base* e = new derived();
base* b = d;
cout << d->base::num <<' ' <<d->num << endl; //编译通过
cout << b->derived::num << endl; //error : 限定名不是类"base"或其基类的成员;
e->show(); //结果:"showDerived";
e->derived::show(); //error : 限定名不是类"base"或其基类的成员;
return 0;
}
友元
友元提供了一种普通函数或者类成员函数访问另一个类中的私有或保护成员的机制。也就是说有两种形式的友元:
友元函数:普通函数对一个访问某个类中的私有或保护成员。
友元类:类A中的成员函数访问类B中的私有或保护成员
优点:提高了程序的运行效率。
缺点:破坏了类的封装性和数据的透明性。
总结:
1.能访问私有成员
2.破坏封装性
3.友元关系不可传递
4.友元关系的单向性
5.友元声明的形式及数量不受限制
友元关系没有继承性,假如类B是类A的友元,类C继承于类A,那么友元类B是没办法直接访问类C的私有或保护成员。
友元关系没有传递性,假如类B是类A的友元,类C是类B的友元,那么友元类C是没办法直接访问类A的私有或保护成员,也就是不存在“友元的友元”这种关系。
使用场景
基类虚函数为私有时
基类虚函数为私有时,需要把使用该类对象的函数设为友元函数。
#include<iostream>
using namespace std;
class Derived;
class Base {
private:
virtual void fun() { cout << "Base Fun"; }
friend int main();
};
class Derived: public Base {
public:
void fun() { cout << "Derived Fun"; }
};
int main() {
Base *ptr = new Derived;
ptr->fun();
return 0;
}
预编译指令
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
using使用
命名空间
#include <iostream>
#define isNs1 1
//#define isGlobal 2
using namespace std;
void func() {
cout<<"::func"<<endl;
}
namespace ns1 {
void func() {
cout<<"ns1::func"<<endl;
}
}
namespace ns2 {
#ifdef isNs1
using ns1::func; /// ns1中的函数
#elif isGlobal
using ::func; /// 全局中的函数
#else
void func() {
cout<<"other::func"<<endl;
}
#endif
}
int main() {
/**
* 这就是为什么在c++中使用了cmath而不是math.h头文件
*/
ns2::func(); // 会根据当前环境定义宏的不同来调用不同命名空间下的func()函数
return 0;
}
改变访问性
私有继承的子类可以通过using访问父类的public和protected。
class Base{
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base {
public:
using Base::size;
protected:
using Base::n;
};
函数重载
函数重载时如果只改变其中一个或几个,需要保留其他同名函数的可使用性,则可以使用using。
class Base{
public:
void f(){ cout<<"f()"<<endl;
}
void f(int n){
cout<<"Base::f(int)"<<endl;
}
};
class Derived : private Base {
public:
using Base::f;
void f(int n){
cout<<"Derived::f(int)"<<endl;
}
};
代替typedef
typedef vector<int> V1;
using V2 = vector<int>;
enum枚举类型
decltype查询表达式类型
与typdef/using合用于定义类型
vector<int> vec;
typedef decltype(vec.begin()) vectype;
与auto合用于追踪函数返回值
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}