CPP基础知识

CPP 基础知识(一)

author:dr@g0n

由于一些原因需要用到cpp的知识,所以在此处记录学习,更多更新内容请看yuque(https://www.yuque.com/along-ojern/mob3of/psynut)
此学习小结,更具Cherno的youtube教程总结而来,他的cpp教程简洁易懂,十分适合有一定开发编程经验(最好懂点汇编和英语)的同学(https://www.youtube.com/watch?v=18c3MTX0PK0&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4)

Visual Studio

创建项目后,vs会自动生成一些文件.
image.png
sln solution 文件 保存项目结构
filters
vcxproj
vs自动生成的文件结构非常混论,建议点击显示所有文件按钮后,添加src文件夹存放源代码。
image.png
常用项目配置
image.png

编译器

一、预编译 preprocessor

1、#define

#define INTEGER int

//编译器在预编译时会将define修饰的内容全部替换,
//此处例子中会将INTEGER替换为int
INTEGER Multiply(INTEGER a,INTEGER b){
}

2、#include

编译器在预编译时会将include的文件内容复制到当前文件中。cpp标准库没有文件后缀,例如iostream ,而c语言标准库又.h后缀,例如stdlib.h。

#include<iostream> //特点include路径用<>  
#include "test.h"  //相对路径用""

3、#if endif

#if condition
blablabla  //只有当condition为真时,此内容才会被编译
#end if 

4、#pragma once

只include该文件一次,往往出现在.h文件中,防止头文件被include到同一个cpp中多次。可以代替以前#ifndef #endif的作用,起到头文件保护的作用。

#ifndef _FLAG_H
#define _FLAG_H
blablabla  //当该文件被include一次后_FLAG_H就已经被define了,
           //如果第二次被include,blabla内容就不会执行
#endif

二、编译为obj (translation unit)

obj file 就是一堆只有cpu认识的二进制数,可以在项目属性中更改输出文件,让编译器输出asm代码,使其具有可读性
image.png
image.png

链接器 Linking

把多个obj文件,及程序所要用到的库链接在一起
.h 文件放函数declaration

#pragma once
using namespace std;
void countStr(const string str);

.cpp放函数

#include <iostream>
#include <string>
#include "countStr.h"

using namespace std;


int main(char argc, char** argv) {
	string str;
	cin >> str;
	countStr(str);
}
#include <iostream>
#include <string>


using namespace std;

void countStr(const string str) {
	for (int i = 0; i < str.length(); i++) {
		cout << i << endl;
	}
}


头文件 .h
一般用来放函数声明,然后就可以在需要调用的地方#include “***.h”

指针(Pointer)

指针是cpp中最重要的概念之一,本质上来说指针是一个存储变量内存地址的整数,指针类型对指针本身并无任何意义,他只是告诉编译器指针指向变量的大小。

#include<iostream>
#define LOG(x) cout<<x<<endl;
using namespace std;

int main(int argc, char** argv) {
	char* buffer = new char[8];   //在堆(heap)内创建8字节空姐
	memset(buffer, 0, sizeof(buffer));//将buffer置为0
	LOG(buffer);
	char** buffer_ptr = &buffer;//指向指针buffer的指针
	LOG(buffer_ptr);
	**buffer_ptr = 10;//改变的是指向buffer指针对应的值,从0变为10
	LOG(buffer);
}

image.png

#include<iostream>
#define LOG(x) std::cout<<x<<std::endl;

int main(int argc, char** argv) {
	char* buffer = new char[8];
	memset(buffer, 65, sizeof(buffer));
	LOG(buffer);  //输出指针指向的8字节内存中的值,因为是char,所以用ascii解析
	char** buffer_ptr = &buffer;
	LOG(buffer_ptr);
	**buffer_ptr = 97;
	LOG(*buffer); //只输出指针指向的内存地址的值,因为是char类型,只有1字节,并用ascii解析
}

image.png

cpp中的const只修饰其右边的内容

const int* a = new int;
b=2;
*a = 2;//报错,因为const修饰了*a其为常量,即不能更改指针指向地址的值
a = (int*)&b;//不报错,这里我们更改了指针指向的地址
int* const a = new int;
*a = 2;//不报错,因为const修饰了a即指针
a = (int*)&b;//报错

引用(reference)

cpp中的引用本质上就是指针,是削弱后的指针。引用必须引用一个已存在的变量,即必须初始化,并且初始化后不可改变。引用本身不是变量,可以把他当成变量的别名。

int& ref = a; //ref必须先初始化,即赋一个变量。

面向对象编程

一、类(class)

本质上说class是一种将数据(data)和函数(methods、funcitons)组织在一起的数据结构。class中的成员变量默认访问控制为private。

#include <iostream>

class Player {
public:
	int x;
	int y;
	float speed;

	void Move(int xa, int ya) {
		x += xa * speed;
		y += ya * speed;
	}
};
int main(int argc, char** argv) {
	Player player1;
	player1.x = 1;
	player1.y = 1;
	player1.speed = 3;
	player1.Move(2, 2);
	std::cout << player1.x << std::endl;
	std::cout << player1.y << std::endl;

}

1、类(class)与结构体(struct)区别

类默认成员变量为private,而struct默认为public。一般情况下,struct是数据的结构体,仅仅用于存放pod(plain old data),例如数学中的向量Vector概念就可以用struct编写。简而言之,struct和class其实可以相互替换,但是为了代码可读性及逻辑通顺,不建议这样做,struct就让他处理一些不复杂(逻辑上的)的数据。

struct Vec{
    float x;
    float y;
    
    void Add(Vec& other){
        x+=other.x;
        y+=other.y;
    }
};

2、static关键字

static可以放在类和结构体中,也可放在之外。
static在类外表示,static修饰的符号在link(链接)阶段是局部的,即只对定义它的编译单元可见(.obj)。

//Static.cpp
int s_Var = 5;
//Main.cpp
int main(){
    int s_Var = 10; 
     // crtl+f7 编译单个文件不会有问题,但是f5编译并链接项目时,由于Static.cpp中已经存在s_Var
     //因此编译器会报“找到一个或多个多重定义的符号”错误,但是当我们修改Static.cpp中s_Var为static时
     //由于static修饰的符号在link(链接)阶段是局部的,即只对定义它的编译单元(.obj)、声明他的cpp文件可见
     //因此不会报错。
     //ps(如果我们想在一个cpp中调用另一个cpp中的全局变量可以用extern关键字,该关键词可以让编译器在另外的编译单元(obj)中找定义,相当于变成了该变量的引用)
}
    

总结:尽量让全局变量和函数static,除非你想在其他cpp文件中要到他。

static在类和结构体中表示,这部分内存是由这个类的所有实例共享的。静态方法里没有该实例的指针,即this。

#include <iostream>

class Singleton {
public:
	static Singleton& Get() {
		static Singleton instance; 
		return instance;
	}
	void Hello() {
		std::cout << "hello" << std::endl;
	}
};

int main(int argc,char** argv){
        Singleton::Get().Hello();
}



//第二种返回Singleton实例的方式
class Singleton {
public:
	static Singleton* instance;

	static Singleton& Get() {	
		return *instance;
	}
	void Hello() {
		std::cout << "hello" << std::endl;
	}
};

Singleton* Singleton::instance = nullptr;
int main(int argc,char** argv){
        Singleton::Get().Hello();
}

cpp中每个类中的非静态方法总是将当前类的实例当作参数传入。因此静态方法无法读取类中的非静态变量。
对于类需要用到但是类实例之间不变的变量可以把他设置为static。

static void Print(Entity e){ // 非静态方法本质上是会传入当前Entity实例
     std::cout<<e.x<<endl;   
}

3、构造函数(Constructors)

实例化对象时会运行,与类的名称一致。默认情况下cpp中的类会有一个空的构造函数,但是你也可以删除它, Log()=delete;

4、析构函数(Destructors)

对象(实例)被摧毁时调用,当你在堆(heap)上开放内存空间时,你必须手动销毁他,此时析构函数就很有用了。

class Demo(){
public:
    int a;
    int b;
    Example e;
    //一定用下面这种方式初始化构造函数,可以节约资源
    Demo(int aa,int bb):a(aa),b(bb),e(Example(8)){}//构造函数,这里时cpp的赋值语法,这里Example只会被创建一次
    
    Demo(int aa){ //与上面类似,但不完全等价,在这里Example会被创建实例两次,前一次的实例被Example(8)构造函数覆盖掉
         e=Example(8) 
         a=aa;   
    }
    ~Demo(){}//析构函数
};

5、继承

类之间存在经常需要复用的内容时可以用继承。可以先创造一个基类(模板),再用子类继承它。

class Base{
public:
    float x;
    float y;
    
    void BaseFunc(){std::cout<<"base func"<<std::endl;}
};

class Son : public Base{
public:
    char* name;
    
    void SonFunc(){std::cout<<"Son func"<<std::endl;}
};

6、虚拟函数(Virtual Function)

虚拟函数起到动态调度的作用,会编译一个V-table,v-table会保存一个基类中所有虚拟函数的映射关系。如果需要override一个函数,你需要再父类中的那个被复写的函数前加上关键词virtual。

#include<iostream>
#include<string>

class Entity {
public:
    virtual std::string GetName() { return "Entity"; }
};

class Player : public Entity {
private:
    std::string m_name;
public:
    Player(const std::string& name) :m_name(name) {}
    std::string GetName() override { return m_name; }
};


void printName(Entity* entity) {
	std::cout << entity->GetName() << std::endl;
}
int main(int argc, char** argv) {	
    Player* p = new Player("Dragon");
	Entity* e = new Entity();
	printName(e);
	printName(p);
}

7、纯虚拟函数(接口)

cpp没有interface关键字,但是可以把纯虚拟函数当接口的抽象方法,继承自纯虚拟函数所在类的子类必须实现纯虚拟函数,才能创造实例。

class Entity {
public:
    virtual std::string GetName() = 0; //让virtual 函数体=0即为纯虚拟函数。
};

class A{
public:
    std::string GetName()override{} //必须实现该纯虚拟函数 
};

8、public、private、protected

private对子类都不可见,且被private修饰的变量和方法在外部不能被调用。
protected对子类可见,但不能在外部被调用。

9、const、mutable关键字

当类方法不应该修改类,那么记住将方法标记为const,这样就保证调用者不能利用方法修改类中的内容

class Entity{
private:
    int X;
    mutable int var;
public:
    int getX() const{ //这里的const,代表getX函数为read-only函数,不能对X有任何改变,即X=2,会报错
        var = 2;//这里不会报错,因为var被mutable修饰,使其可以在const函数中被更改。
        return X;
    }

10、创建实例方法

分配给堆会比分配给栈使用更多时间,同时堆需要我们手动销毁。只有在class非常大和我们需要手动控制销毁时刻时再用new。

{
    Entity e = Entity("dragon"); //栈上创建实例,随着栈被销毁而消失
}

{
    Entity* e = new Entity("dragon"); //new堆上创建实例,只有我们delete了才会销毁,注意new返回的是实例所在的地址,即指针
    std::cout<<e->getName()<<std::endl;
}
delete e;//需要我们手动销毁

二、特殊类

1、Enum(枚举类)

enum 相当于给了integer一个名字

enum Test: unsigned char{  //默认是4字节int ,不能是浮点类型
    
};

//eg.log 类
class Log {
public:
	enum Level {
		LevelError, LevelWarning, LevelInfo
	};
private:
	Level m_LogLevel= LevelInfo;
public:
	void setLogLevel(Level LogLevel) {
		m_LogLevel = LogLevel;
	}
	void Error(const char* messages) {
			if(m_LogLevel>= LevelError){
				std::cout << "[Error]" << messages << std::endl;
		}
	}

	void Warn(const char* messages) {
		if (m_LogLevel >= LevelWarning) {
			std::cout << "[Warn]" << messages << std::endl;
		}
	}

	void Info(const char* messages) {
		if (m_LogLevel >= LevelInfo) {
			std::cout << "[Info]" << messages << std::endl;
		}
	}
};

int main(){
    Log log;
	log.setLogLevel(Log::LevelInfo);
	log.Error("error msg");
	log.Warn("warn msg");
	log.Info("info msg");
}

数据结构

一、数组(Array)

1、创建数组

static const int exampleSize=5

int example[exampleSize] = {1,2,3,4,5}; //数组在栈中,随着函数销毁而销毁
std::array<int,5> example2;//cpp11后可以用此种方式,更安全但是开销更大
int* example3 = new int[5]; //数组在堆中,需要手动销毁
delete[] example3; //销毁用new创建的数组

例子中的example实际是指针*(example+2)=5example[2]=5等价。
如果你在函数中创建并需要返回一个数组,那必须使用new关键词创建。

数据类型

一、字符串(string)

字符串本质上就是字符的集合,

"dragon" // 类型为const char[7] ,之所以是7是因为有一个\0截止符号
char* name = "dragon";
char[7] name2 = {'d','r','a','g','o','n',0};//0是字符串的截止符号
std::string name = std:string("hello") + "drasgon";
std::string name = "hello"s + "drasgon";


//常用方法
name.find("a");//查找字符出啊
bool contains = name.find("a") != std::string::npos;	
name.size();//判断大小

char* a = "qwe";
const char* a = "qwe";//正确写法
char* a = (char*)"qwe";//正确写法
a[2] = 'a';//这样的操作是不被允许的,因为qwe是字符串文本保存在内存中的“只读”部分.


//char 每个字符1个字节   utf-8
const char* name1 = u8"dragon";

//wchar_t   每个字符2个字节
const wchar_t* name2 = L"dragon";

//char16_t  每个字符2个字节   utf-16
const char16_t* name3 = u"dragon";

//char32_t  每个字符4个字节   utf-32
const char32_t* name4 = U"dragon";

//可以整段打印
const char* example = R"(line1 
line2
line3
line4)";

二、整数类型

1字节(byte)=8比特(bit)=2个16进制数
cpp是一门很强大的语言,仅有非常少的规则限制,对于变量来说cpp变量之间唯一的区别就是大小。

1、变量大小

char 1字节
int 4字节
short 2字节
long 4字节
long long 8字节
float 4字节
double 8字节
bool 1字节

float a = 5.5f;
double a = 5.5;

CPP特殊语法

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值