C++基础语法

参考资料:


  • 为什么学习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 <iostream>
#include <stdint.h>
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 <array>
int main()
{
    std::array<int, 10> buff;
    for (int i = 0; i < buff.size(); i++) {
        buff[i] = i;
    }

    return 0;
}
  • String
    const char *string = "hahaha"; //这里字符串是存放在只读存储器
    以'\0'结尾。只是一个指针。不能修改string[2]
#include <iostream>
#include <string>
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 <iostream>

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 <iostream>
#include <stdint.h>
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<Entity> e0(new Entity());//right
        //std::unique_ptr<Entity> e = new Entity();//wrong
        std::unique_ptr<Entity> e1 = std::make_unique<Entity>();//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<Entity> 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<Entity> 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<string,int> 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<int>(6);但C++会根据输入的类型自动生成int,所以下面不用,但是要知道print是多个函数。
template <typename T>
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 <typename T,int N>
class Array {
private:
    T m_Array[N];

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

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

  • 返回多个返回值
  • 为什么要使用static array而不是普通数组?
    1.array在这里是一个类,但由于是模版生成的,占用的内存跟标准数组是一样的,比如虽然有.size(),size并不会占用内存空间,因为模版生成的时候直接返回模版输入。
    2.功能更强大,在debug会检测出数组越界,可以获取数组长度等等。
#include <array>
int main(int argv, char** argc) {
    std::array<int, 5> 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容