C++基础语法

参考资料:


C++基础语法_第1张图片
  • 为什么学习C++
    C++可以直接控制机器,效率比较搞,C++代码经过对应的编译器编译就得到机器码了。C#、java会经过虚拟机

  • C++怎么工作起来的
    main函数可以虽然有声明要int返回值,但是可以不返回,会默认返回0,只有main函数有这个特例。
    << 运算符其实是函数
    hpp文件不会编译,cpp文件各自都编译为单独.obj文件,链接将这些.obj文件连接起来为.exe文件
    函数声明告诉链接器存在这个函数,链接器后面会去找,找不到会得到链接错误。

  • 编译
    预处理,比如#include将整个文件复制过来,设置好VS可以得到已经预处理完的.i文件。

  • 链接
    将编译好的多个文件编译成一个。
    编译错误unresolved external symbol,找不到定义函数,可能是没有声明或者定义函数

  • 变量
    变量不同在于内存的大小

  • 头文件
    如果一个函数要在其他cpp文件被用到,需要在其他cpp文件中声明说,有这个函数。如果没有#include 头文件,那么每次使用都需要写函数声明。
    hash pragma once 头文件在cpp文件中只包含一次

  • 指针
    就是一个整数,存放着变量的地址
    指针的指针说法其实有问题,指针是个常量,没有地址,应该是指针变量的指针

  • 引用
    不占用空间,不是变量,不能改变,指针的简化版本,功能简单点,使用简单点。
    下面第二句只是给a变量赋值,不是改变引用的值,引用不是变量,不能改变。

int& ref = a;
ref = b;

  • 类不是必要的,但可以让程序更简洁,像C程序也可以完成很多工作。

  • static
    在类之外,限制定义全局变量,只定义在一个文件之内,不会互相影响。这里注意scope和lifetime的区别,scope是在一个文件,lifetime是伴随着整个程序。如果不同文件没有用static定义相同的变量会产生链接错误。函数也是一样的。
    在类之内,只要是用类都可以用到。w
    在函数之内,scope是函数体内部,lifetime是从函数开始创建到程序结束。local变量单例模式使用如下。

class Player {

public:
    static Player& get() {
        static Player p;
        return p;
    }

    void move() {

    }
};


int main()
{
    Player p = Player::get();
    p.move();

    return 0;
}
  • enum
    作用:给值赋予一个名字,是一种新的类型,可以限定对应的变量是否在定义的那几个数的范围之内,比如下面的Level level; level只能是Level中的其中一个值。其中引用的时候也可以用类来引用 Log::LogLevelWarn
#include 
#include 
using namespace std;


class Log {
public:
    enum Level {
        LogLevelInfo = 0,
        LogLevelWarn = 1,
        LogLevelError = 2
    };

private:
    Level mLogLevel = LogLevelInfo;

public:
    void setLevel(Level level) {
        mLogLevel = level;
    }

    void info(const char *message) {
        if (mLogLevel <= LogLevelInfo) {
            cout << "[INFO]: " << message << endl;
        }
    }

    void warn(const char *message) {
        if (mLogLevel <= LogLevelWarn) {
            cout << "[WARN]: " << message << endl;
        }
    }

    void error(const char *message) {
        if (mLogLevel <= LogLevelError) {
            cout << "[ERROR]: " << message << endl;
        }
    }
};


int main(int argv, char** argc) {
    Log logger;
    logger.setLevel(logger.LogLevelWarn);
    logger.info("info");
    logger.warn("warn");
    logger.error("error");

    system("pause");
}
  • 构造器
    很多时候,我们都会在某个东西创建完成后希望完成初始化,M.init(),但有时会漏掉,构造器正是这个用处。构造器和析构器有点回调的意味,会自动在构造和free的时候调用。

  • 析构器的一个应用
    释放对象相关分配的内存

  • 继承
    继承可以减少重复代码

  • 虚方法
    应用场景:一个鸭子类继承自动物类,如果重写了move方法,鸭子对象赋给动物类型,调用move会用动物.move,但实际上我们希望调用鸭子.move,C++可能为了高效,所以才会有这样的问题,虚函数会建立一些数据结构,然后调用的时候会先从鸭子开始查起来,这样才对,但性能会略微损失。给基类方法加上virtual,会建立V table,继承的信息都放在V table里面,调用的时候先从子类的方法开始。

  • 纯虚方法
    相当于java中抽象方法或者接口

  • 可见性
    我们需要可见性,因为对于类之外的继承者,使用者来说,需要屏蔽掉一些东西,不能在外部使用,不然程序可能会出现奔溃等等。比如游戏的角色有坐标x,y,如果外面随意修改这些值,角色可能不会像我们想象的那样子移动,类如果可以让我们操作角色移动,那么一定提供了更好的方法。

  • 数组
    int example[5];//在栈上分配内存
    int[] example = new int[5];//在堆上分配内存,需要delete[] example

#include 
int main()
{
    std::array buff;
    for (int i = 0; i < buff.size(); i++) {
        buff[i] = i;
    }

    return 0;
}
  • String
    const char *string = "hahaha"; //这里字符串是存放在只读存储器
    以'\0'结尾。只是一个指针。不能修改string[2]
#include 
#include 
int main()
{
    std::string str = "Hello,World!";//helloworld这里是const char *指针
    std::cout << str << std::endl;

    std::cin.get();
    return 0;
}

方法后面加const不能修改类元素

void method() const
{
}

上面函数在下面中有用e.method()必须是上面的const修饰的,保证不会修改到e的内容才可以允许调用,不然会出错。

void function(const Entity& e){
  e.method()
}

但是呢?被method后面被const修饰还有办法修改类的一些变量,这些变量要用mutable修饰。

  • Member Initializer Lists
    构造函数:m_member(value)
    可以令初始化更简洁
    可以减少拷贝,下面代码没有使用成员初始化列表,Example产生了两次,使用成员初始化列表只会产生一次。
#include 

class Example {
public:
    int m_member;

    Example() {
        std::cout << "UNKNOWN" << std::endl;
    }

    Example(int x) {
        m_member = x;
        std::cout << "create with x" << std::endl;
    }
};


class Entity {
public:
    Example ex;

    Entity() {
        this->ex = Example(2);
    }

};

int main()
{
    Entity e;


    std::cin.get();
    return 0;
}

把Entity改为下面只会生成一个

class Entity {
public:
    Example ex;

    Entity() :
        ex(Example(2))
    {
    }

};
  • 实例化对象
    Entity e;这个在C++中是在栈中生成一个对象,可以直接使用,但是在java中是一个空指针,需要进一步new 对象。
    也可以是Entity e("xxxx");和Entity e = Entity("xxxx");
    堆中实例化 Entity *p = new Entity();
    java过来的人可能会到处new,但这在C++中并不好,因为java会自动回收,但C++不会,所以还是该用栈就用栈.

  • new
    分配内存,调用构造函数
    new 的时候有用[],delete也要[]

  • 运算符重载
    有时函数的调用不是很清晰,比如A.add(B.mul(C)),用运算符就是很清晰,A+B*C。但是如果人家看你代码要去看运算符重载的部分,说明你可能用的不合适。

class Vect2 {

public:
    double x, y;

    Vect2(double x, double y)
        : x(x), y(y) {  }

    //第一个const代表不能修改进来的引用,第二个const表示不能修改此对象的任何值
    Vect2 operator+(const Vect2& other) const{
        return Vect2(x + other.x, y + other.y);
    }

    Vect2 operator*(const Vect2& other) const {
        return Vect2(x * other.x, y * other.y);
    }

};

std::ostream& operator<<(std::ostream& stream, const Vect2& other) {
    stream << other.x << "," << other.y;
    return stream;
}


int main(int argv, char** argc) {
    Vect2 v1(1.0 , 1.0);
    Vect2 v2(2.0, 2.0);
    Vect2 v3(3.0, 3.0);

    Vect2  v4 = v1 + (v2*v3);

    cout << v4 << endl;

    system("pause");
}
  • const
    Entity* const & e = this;
    Entity* const e = this;
    上面两个式子是等价的,而且不允许 e 和 this 的赋值。

  • 智能指针
    删掉指针同时自动free指向的内存,。
    如果使用Entity* e = new Entity();,main中new是在堆分配的,不会被释放,内存泄漏。
    同时注意ScopedPtr e = new Entity();采用了隐式转化,因为右边是一个Entity*类型,左边是ScopedPtr,那怎么还可以赋值呢?这实际上相当于ScopedPtr e(new Entity());
    智能指针结合了栈和生成器,析构器的特点,跟回调是一样的,同样还可以用于一段程序的时间测量,也可以用锁锁住一小段程序。

#include 
#include 
using namespace std;


class Entity {

};

class ScopedPtr {
private:
    Entity* mPtr;

public:
    ScopedPtr(Entity *e) {
        cout << "create pointer" << endl;
    }

    ~ScopedPtr() {
        delete mPtr;
        cout << "free memory" << endl;
    }
};


int main(int argv, char** argc) {

    {
        ScopedPtr e = new Entity();
    }

    system("pause");
}
  • unique pointer
    系统的另外一种智能指针,这种指针不能被复制。
    unique 指针,只能有一个,因为该指针内存被free了,其他指向同一个内存的指针就废了。
class Entity {

public:
    Entity() {
        std::cout << "construction" << std::endl;
    }

    ~Entity() {
        std::cout << "deconstruction" << std::endl;
    }
};

int main()
{
    {
        std::unique_ptr e0(new Entity());//right
        //std::unique_ptr e = new Entity();//wrong
        std::unique_ptr e1 = std::make_unique();//also right
    }
    std::cin.get();
    return 0;
}

这种指针无法赋值,因为复制号重载被删掉了。

  • 复制构造器
    第45集,非常精彩。
    下面的代码会抛出异常,因为e1是复制e0的,e1只是分配内存后复制了e0的全部内容,故公用new int[5]内存块,但是跳出main的时候会free两次,这个时候要重写复制构造器,然后进行分配内存和深复制。
class Entity {
public:
    int *m_Buff;

    Entity() {
        m_Buff = new int[5];
    };

    ~Entity() {
        free(m_Buff);
    }

};

int main(int argv, char** argc) {
    Entity e0;
    Entity e1 = e0;
}

增加复制构造器

class Entity {
public:
    int m_size;
    int *m_Buff;

    Entity() {
        m_size = 5;
        m_Buff = new int[m_size];
    };

    ~Entity() {
        free(m_Buff);
    }

    //:之后进行浅复制,复制构造函数里面深复制
    Entity(const Entity& other) : m_size(other.m_size) {
        cout << "进行复制ing..." << endl;

        m_Buff = new int[5];
        memcpy(m_Buff, other.m_Buff, m_size);
    }
};

int main(int argv, char** argc) {
    Entity e0;
    Entity e1 = e0;

    system("pause");
}
  • 参数传入用const引用 const Type&

下面的程序输出三句进行复制ing...,说明复制了三次,原因就是调用函数的时候复制了两次,我们不希望在这里消耗性能,因此参数传入改用const引用。

class Entity {
public:
    int m_size;
    int *m_Buff;

    Entity() {
        m_size = 5;
        m_Buff = new int[m_size];
    };

    ~Entity() {
        free(m_Buff);
    }

    //:之后进行浅复制,复制构造函数里面深复制
    Entity(const Entity& other) : m_size(other.m_size) {
        cout << "进行复制ing..." << endl;

        m_Buff = new int[5];
        memcpy(m_Buff, other.m_Buff, m_size);
    }
};


void doEntity(Entity e) {

}


int main(int argv, char** argc) {
    Entity e0;
    Entity e1 = e0;

    doEntity(e0);
    doEntity(e1);

    system("pause");
}

将上面的函数改为下面只会复制一次!

void doEntity(const Entity& e) {

}
  • 栈与堆的区别
    除了使用上的区别,博主主要讲了效率上的区别,栈很简单,就是移动栈顶指针,而堆需要分配内存,这个就比较耗时了。

  • auto
    主要是在变量类型明显,且比较长的时候可以省略变量类型。
    记住返回引用类型的时候要用auto&

  • vector的使用,实际上叫ArrayList更准确,即可以改变长度的数组
    这个例子用法很低效率,看下一个例子的优化

class Entity {
public:
    int m_size;
    int *m_Buff;

    Entity(int m_size) {
        this->m_size = m_size;
        m_Buff = new int[m_size];
    };

    ~Entity() {
        free(m_Buff);
    }

    //:之后进行浅复制,复制构造函数里面深复制
    Entity(const Entity& other) : m_size(other.m_size) {
        cout << "进行复制ing..." << endl;

        m_Buff = new int[5];
        memcpy(m_Buff, other.m_Buff, m_size);
    }
};


ostream& operator<<(ostream& stream, const Entity& e) {
        stream << "m_size : " << e.m_size << endl;
        return stream;
}

int main(int argv, char** argc) {
    vector entities;
    entities.push_back(1);
    entities.push_back(2);
        entities.push_back(3);

    //删除第二个
    //entities.erase(entities.begin() + 1);

    //注意这里要用const Entity& e才不会复制多次
    for(const Entity& e:entities)
        cout << e;

    system("pause");
}

  • vector的优化使用
    上面例子中,塞进去三个元素,复制了6次!!
    为什么要复制,因为其原理是首先在栈中创建对象,然后复制到vector的对应的内存单元中——堆,entities.push_back(3);其实是entities.push_back(Entity(3));;为什么是6次,vector首先分配固定的空间,不够了就再分配一个更大的空间,然后复制过去,几次下来就复制很多次。
    下面的代码复制0次,首先我们一开始预留了足够的3个空间,如果接下来按照前面的做法是要复制3次,但是这次我们使用emplace_back,没有经过栈,直接在堆上面创建起来。
int main(int argv, char** argc) {
    vector entities;
    entities.reserve(3);
    entities.emplace_back(1);
    entities.emplace_back(2);
    entities.emplace_back(3);
}
  • 返回多个值
    1.返回一个结构体
    2.输入各个类型变量的引用,然后再函数中
    【注】:由下面的观察可知,变量名就是引用;另外getTwoString中,a = string("aaaa");是先创建一个字符串,然后再复制给a!!从一个栈复制到另外一个栈。
void getTwoString(string& a,string& b) {
    a = string("aaaa");
    b = string("bbbb");
}

int main(int argv, char** argc) {
    string a, b;
    getTwoString(a, b);

    cout << a <<  endl << b << endl;

    system("pause");
}

3.数组,vector两种方式。
4.tuple,跟python很像。

tuple getStringAndInt() {
    //猜想这里string("Hello")产生在栈上,然后复制到make_pair内部的从堆上分配的内存
    return make_pair(string("Hello"), 111);
}


int main(int argv, char** argc) {
    auto source = getStringAndInt();
    string str = get<0>(source);
    int inter = get<1>(source);

    cout << str <<  endl << inter << endl;

    system("pause");
}
  • 模版入门:
    编译器帮你写程序,模板只是一个蓝图。在调用的时候根据类型再去生成。
    如果写一个打印多种数据类型的函数
    void print(T content)中的T随着调用的类型而变成特定的类型,这个函数不是我们平常的函数,这是一个模版,在编译的时候如果没有调用函数,那么不存在,错了也没有关系,如果调用,那么就会实际生成一个函数,这跟我们自己写的效果一样,只是这样我们省了很多工作,模版实际上在帮我们写代码。实际上函数调用的时候也可以写成print(6);但C++会根据输入的类型自动生成int,所以下面不用,但是要知道print是多个函数。
template 
void print(T content) {
    cout << content << endl;
}

int main(int argv, char** argc) {
    print(6);
    print(3.1415);
    print("hello!");

    system("pause");
}

再来一个例子:

class Entity {
private:
    int m_Num;

public:
    Entity(int num) {
        m_Num = num;
    }
};

template 
class Array {
private:
    T m_Array[N];

public:
    int size() { return N; };
};

模版的弊端:可能导致程序很复杂,因为代码都是模版自动生成出来的

  • 返回多个返回值
  • 为什么要使用static array而不是普通数组?
    1.array在这里是一个类,但由于是模版生成的,占用的内存跟标准数组是一样的,比如虽然有.size(),size并不会占用内存空间,因为模版生成的时候直接返回模版输入。
    2.功能更强大,在debug会检测出数组越界,可以获取数组长度等等。
#include 
int main(int argv, char** argc) {
    std::array data;
    data[0] = 1;

    //这里会报错
    data[5] = 1;

    system("pause");
}
  • 隐式转化
    40集 Implicit Conversion and the Explicit Keyword in C++
    奇怪的语法
class Entity {
private:
    std::string name;
    int age;

public:
    Entity(std::string name) : name(name),age(-1){}
    Entity(int age) :age(age),name("UNKNOWN"){}

};

void printEntity(const Entity& e) {
    //...
}


int main()
{
    Entity e1("Mike");  //normal1
    Entity e2(22);  //normal2
    Entity e3 = "Stuart"; //"Stuart"这里是一个字符串类型,隐含转化
    Entity e4 = 22; //隐含转化
    Entity e5 = (Entity)22; //强制类型转化

    printEntity(22); //隐含转化
    printEntity("Ada"); //Error!"Ada"是const char[] 类型,隐含转化只能转化一次,不从const char->string->Entity
    printEntity(std::string("Ada")); //normal
    printEntity(Entity("Ada")); //normal
}

explicit 放在构造器前面,说明这个构造器就不能再进行隐含转化,22 -> Entity 是调用了Entity(int age)进行生成一个Entity对象的,如果变成explicit Entity(int age)那么Entity e4 = 22;printEntity(22);会失败,但是强制类型转化还是可以的。implicit意味着自动。

  • cast

你可能感兴趣的:(C++基础语法)