我的c++学习笔记

程序运行内存:

程序运行时,计算机会预留一段内存给它,1、程序指令,计算机需要把这些指令加载到内存   2、静态或全局变量    3、栈区   4、堆区    

编译,链接,动态,静态库:

        动态:程序运行时再去进行链接                静态:事先编译链接到exe程序中

        lib文件:包含了dll中所有函数、变量的位置         dll文件:动态链接时用到         dll.lib文件:包含了dll中函数和符号的指针。与dll一起用,这样就不用询问dll里是否有某些函数的指针

         如果某函数、变量是某文件里的,可用extern,表示从外部寻找该定义;

        static用在链接时,意思是此变量或者函数只对本文件可见,对其他文件不可见;此外,static也相当于局部变量的意思,作用域为局部。

                 inline可以直接使用此函数,将调用的函数,直接换成该函数体

                #pragma once表示只文件被包含一次;防止多次include该文件;

头文件相关:

        <>表示编译器的绝对路径,也就是编译器包含了的路径,而  “”  表示从相对路径找,但是也可从绝对路径找;也就是说“” 的范围比<>大。   

              .h后缀的文件是c标准库的,而没有.h是c++标准库的 。相当于区分他们属于哪个标准库。

结构体和类的区别:

        技术上讲,本质区别是结构体成员的默认为public,class类成员默认为private。

        保留这两者的原因是保证c和c++的兼容性,因为c中没有类,只有结构体。

        使用习惯上,个人偏向于:结构体尽量保证简洁,只对数据进行操作,复杂性小,不用它来做太多的事情。如果需要用到继承之类的结构,大量函数操作的话,用类比较好。

char字符串相关;

        如图,如果是const char* &  ref = a,将会报错,因为数组地址其实是不能改变,是const类型的,由此可以得出,如果要创建一个引用,这个引用的类型也要和原始数据一样,比如

const char*  pa = a;

这里的a会 被隐式转换为const char*,所以可以。

const char*&  ref = a;

而ref引用实际上与a的原始类型有关,因为a是数组,a所代表的首地址不能改变,所以要const类型的引用;而前一个const其实可以不要,因为我们可以修改,也可以不修改这个引用的地址的数据;

    char a[] = "char Test";
	const char*  pa = "ss";
	const char* const&  ref = a;

        如何让字符串更快呢?使用std::string_view内存视图,它是一个指向现有内存的指针,也就是一个const char 指针再加上大小size。使用它就避免了分配新的内存,而是直接指向之前的内存。

        string字符串,如果大于设定的值,就会触发堆内存分配,我的电脑是15个字符串。

#include <string>
#include <iostream>

static int a = 0;

void* operator new(size_t size) {
	//std::cout << size << "       分配了\n";
	a++;
	return malloc(size);
}
#if 1
void PrintName(const std::string_view str) {
	std::cout << str << "\n";
}
#else
void PrintName(const std::string& str) {
	std::cout << str << "\n";
}
#endif
void main() {
	//std::string myname = "hys";
	const char* myname = "hys";//这个相当于指向字符串常量区	
	PrintName(myname);
	std::cout << a << "次内存分配\n";
}

static:

        表示静态,作用域有限

        用在类外部时,表示此变量或者函数只对本文件可见,对其他目录的文件不可见;此外,static也相当于局部变量的意思,作用域为局部。

        用在类内部时,变成所有类成员共享此变量,在此类中只有一个实例。静态方法不能访问非静态成员

        局部静态:用在函数中时,他的作用域就在函数内部,当第一次调用该函数时,它会创建一个实例,之后的函数调用都是用的这个实例。生命周期延续到永远

        用法:比如要对结构体的变量进行初始化,用static实现,可以简化很多代码。

New:

        使用new的时候,其实底层是调用了malloc函数,区别在于new创建类对象时,会调用构造函数,而malloc只是返回void类型的指针

        可以通过new()来指定在特定的内存分配空间。

        relloc,malloc,calloc区别:calloc比malloc多了个参数,第一个参数是数量,第二个是大小。最重要的是calloc可以对分配的空间进行初始化默认为0,而malloc不会初始化。

枚举:

             示例:name为该枚举类的名称,int为枚举类型。

enum name :int{
       A,B,C
};

枚举默认从零开始递增。

构造函数和析构函数:

        类或结构体默认有一个无参数的构造函数。如下,构造函数的名称和类名一样,不需要返回类型。可以有多个构造函数,但他们的参数不能一样。如果不想让其他程序创建实例,可以通过把构造函数设为private类型,或者删除构造函数,比如:exam()=delete;

class exam{
    exam(){} //构造函数,创建类对象时调用
    ~exam(){}//析构函数,类被销毁时自动调用              
}
      

隐式构造函数和explict:

        构造函数可以隐式调用,隐式转换只会进行一次,explict关键字使其不能隐式转换;

class examp{
public:
      int a;
      examp(int x)
        :a(x)
        {}
}
void main(){
        examp e1=1;//这里编译器便进行了隐式构造,
        examp e2(2);
}

虚函数和纯虚函数(接口):

        如图,如果构建一个examp2类型的对象,将它作为examp类型的参数时,如果要调用ptr函数。会先从基类的ptr查找,而不是examp2里的ptr函数。使用虚函数可避免这种情况

class examp{
public:
     virtual void ptr(){}//加入virtual关键字,表示为虚函数
     virtual void log()=0;//等于0说明他为一个纯虚函数,纯虚函数必须在子类实现,才能创建类的实例
}
class examp2:public examp{
public:
        void ptr() override{}//可以加入override,增加代码可读性,说明它是一个覆写的虚函数
        
}

        虚函数缺点:会消耗额外内存来生成和存储v表。其次,会消耗时间来遍历v表。不过通常情况下影响很小。   

        纯虚函数必须被实现,才能创建类的实例 。       

初始化列表:

class examp{
    public:
            std::string name;
            int x,y//注意:如果为int* x,y,只有第一个x才是指针,y是int类型的。
     examp()
      :name("myname"),x(1),y(2)//如果name不用初始化列表,会创建两个name实例,然后把一个赋值给另外一个,造成额外的开销。使用初始化列表的话就只会创建一个。此外,int类型的除非给它赋值,不然不会创建实例。
        {
        }
}

        如上,在构造函数后加冒号,然后跟上对象以及参数,就可以把参数赋值给该对象。使用时,要按照参数声明的顺序。因为无论你按什么顺序初始化列表,编译器都会按照声明的顺序来赋值,如果打破这个顺序,可能会导致各种依赖问题。    

  使用初始化列表的方法好处很多。

函数符重载:

        如图:Vector2 operator+ (const Vector2 &v)可以对+进行重载,在使用该类对象的时候,就可以使用+号来进行运算;

class Vector2{
private:
    int x,y;
public:
    Vector2 add(int a,int b)
:x(a),y(b){}

   Vector2 add(const Vector2& v){
        return Vector2(x+v.x,y+v.y);        
}
//重载"+"
    Vector2 operator+ (const Vector2 &v){
        return Vector2(this.x+v.x,this.y+v.y);   
}
void main() {
    Vector2 speed(2,3);//构建一个vector2
    Vector2 position(3,4);//构建一个vector2
    Vector2 result =speed+position;//两个vector2进行运算
)

智能指针:

        作用域结束时自动调用析构函数,delete 指针,释放内存;需要导入头文件,include <memory>;

如图,便创建了两个智能指针,第二种方式更好,注意,智能指针不能隐式调用构造函数;

        智能指针不能进行copy,赋值给其他指针。因为如果赋值,那这两个智能指针指向的是同一块内存,当一个unique销毁时,那另外一个指向的就是被释放的内存。看源码可以发现,其实赋值操作已经被重载删除了;

class Entity{
    private:
        int x;
    public:
        Entity (int a)
        :x(a)
        {
        std::cout<<"create Entity!"<<std::endl;
        }
     ~ Entity (){
        std::cout<<"Destroy Entity!"<<std::endl;
        }
}

    void main(){
        std::unique_ptr<Entity> e(new Entity(2)) ;//创建智能指针e
        std::unique_ptr<Entity> e0 = std::make_unique<Entity>(3);
        //另外一种方式创建智能指针e0,出于异常安全考虑,这种方式更好
}

共享指针、弱指针:

        共享指针功能和unique差不多,不过他在创建的时候,会在内存里建一个引用计数内存块,每次进行赋值操作,引用计数加1,离开作用域,引用计数减1,当引用计数为0时,才会释放该指针。而使用弱指针,比如std::weak_ptr<Entity> e4 = e1;这种方式赋值,不会使引用计数增加;

        和unique一样,不用new方式创建更好,这里是因为share创建的时候,会额外分配一块叫做控制块的内存,用来存储引用计数,如果先new Entity再传给share的构造函数,会有两次内存分配;如果std::shared_ptr<Entity>e3(new Entity(5))这种方式,他就会两者结合,效率更高;

include<iostream>
include<memory>//需要包含该头文件

class Entity{
    private:
        int x;
    public:
    //构造函数
        Entity (int a)
        :x(a){
        std::cout<<"create Entity!"<<std::endl;
        }
    //析构函数
     ~ Entity (){
        std::cout<<"Destroy Entity!"<<std::endl;
        }
}	
void main(){
    std::shared_ptr<Entity>e1 = std::make_shared<Entity>(4);//创建共享指针
	std::shared_ptr<Entity>e3(new Entity(5));//创建共享指针
	std::weak_ptr<Entity> e4 = e1;  //可以进行赋值操作,通过weak类型的,不会使引用计数增加
}

拷贝构造函数:

#include <iostream>
#include <stdlib.h>
class String {
public:
	char* m_buffer;
	int m_size;
public:
	String(const char* other)
	{
		 m_size = strlen(other);
		m_buffer = new char[m_size + 1];
		memcpy(m_buffer, other, m_size + 1);		
	}
	char& operator[](unsigned int i) {
		return m_buffer[i];
	}
	String(const String& other)
		:m_size(other.m_size)
	{
		m_buffer = new char[m_size+1];
		memcpy(m_buffer, other.m_buffer, m_size+1);
	}
};
 std::ostream& operator <<(std::ostream& stream, const String& string) {
	stream << string.m_buffer;
	return stream;
}

void main() {
	String str="cherno";
	String str2 = str;
	str2[1] = 'p';
	std::cout << str <<std::endl; 
	std::cout << str.m_buffer << std::endl;
	std::cout << str2 << std::endl;
}

偏移量:

        如图:0代表地址从0开始,或者是nullptr,数字几就是从几开始计算偏移量,在游戏或者渲染方面会用到;

#include <iostream>
struct  vector3
{
	float x, y, z;
};
void main() {
	int offst = (int)(&(((vector3*)0)->x));
	std::cout << offst << std::endl;
}

Vector:

和array不同,arrayt创建在栈区,vector创建在堆区。通常初始容量是1;在push_backl时,会将参数复制到vector所分配的内存中,会有多余的copy操作;如果vector的数据超出了它的大小,会自动扩容,将旧vector里的数据copy一份到新创建的vector;

        常用:

vector.size(),返回vector大小

vector.push_back(),添加元素

verteies.reserve(3);  预先设定vector的容量

verteies.clear();清空该vector

verteies.erase(verteies.begin());移除某个元素,参数为迭代器

#include <iostream>
#include <vector>
struct Vertex
{
	int x, y, z;
	Vertex(int a, int b, int c)
		:x(a), y(b), z(c) 
	{
		std::cout<<"调用了构造函数"<<std::endl;
	}
	Vertex(const Vertex& v)
		:x(v.x), y(v.y), z(v.z)
	{
		std::cout << "coping!" << std::endl;
	}
};
std::ostream& operator<<(std::ostream &stream,Vertex& v) {
	stream << v.x << "," << v.y << "," << v.z;
		return stream;
}
void main() {
	std::vector<Vertex> verteies;//创建vector数组

//###########优化#################
	verteies.reserve(3);    //预先设定vector的容量,
	verteies.emplace_back(1,2,3);//传入构造函数的参数列表
	verteies.emplace_back(4, 5, 6); //告诉vector在其构建的内存中
	verteies.emplace_back(7, 8, 9);//把这些作为参数, 构建一个vertex对象
	//这样就可以省去很多复制操作
//##########优化###################/
//	verteies.push_back(Vertex(1,2,3));//添加元素
//	verteies.push_back(Vertex(4,5,6));//添加元素
//	verteies.push_back(Vertex(7, 8, 9));//添加元素
	/*for (int i = 0; i < verteies.size(); i++) {
		std::cout << verteies[i] << std::endl;
	}*/
	//增强for循环
	for (Vertex& v :verteies) {
		std::cout << v<< std::endl;
	}
	verteies.erase(verteies.begin());//移除某个元素,参数需要为迭代器
	verteies.clear();//清空该vector
}

模板:

#include <iostream>
#include <string>
template <typename T>
void printer(T x) {
	std::cout << x << std::endl;
}
template <int N>
class array 
{
private:
	int m_array[N];
	int m_size;
public:
	int GetSize(){ 
		m_size =N;
		return m_size;
	};
};
void main() {
	array<5> a;
	printer(1);
	printer(a.GetSize());
}

        自动匹配相应的类型。如果有模板的函数,则函数在编译时不会被创建,也就不会报语法错误。

类型转换:

       c++风格 有4种cast类型转换:static_cast 、const_cast 、dynamic_cast。使用cast的类型转换,可以搜索到在哪些地方使用了这些类型转换,还可以使代码安全友好点。

        dynamic_cast动态类型转换,像一个函数。保证做的类型转换是有效的。他需要用到rtti(运行时的类型信息),然后再判断是不是该类型,所以有运行时的开销

预编制头文件:

        可以将需要大量用到的头文件放在里面,预先编制为二进制文件,这样在编译项目的时候,就不需要重新去预编译这些文件,大大节省编译时间。

        应该放什么文件进去呢? 需要大量用到的头文件,比如标准库,如果是少量用到的头文件或者自己写的,就尽量不要放进去,因为如果改变了,就需要重新预编制。也不该把如何文件都放进去。因为不知道这个cpp文件具体用到哪些头文件,代码复用、重建时会很麻烦

计时器:

#include <iostream>
#include <string>
#include <chrono>

static int a = 0;

void* operator new(size_t size) {
	//std::cout << size << "       分配了\n";
	a++;
	return malloc(size);
}
class Timer {
	std::chrono::time_point<std::chrono::steady_clock> startTime;
	 std::string name;
	//const char* name;
public:
	Timer()
		:startTime(std::chrono::high_resolution_clock::now()),name()
	{
		name = "s";
	}
	Timer(std::string& s)
		:startTime(std::chrono::high_resolution_clock::now()), name(s)
	{

	}
	~Timer() {
		auto endTime = std::chrono::high_resolution_clock::now();//计时器
		long long end = std::chrono::time_point_cast<std::chrono::milliseconds>(endTime).time_since_epoch().count();
		long long start = std::chrono::time_point_cast<std::chrono::milliseconds>(startTime).time_since_epoch().count();
		auto time = end - start;
		std::cout <<name << " 花费了----" << time << "ms(毫秒)\n";
	}
};
void main() {
	{
		std::string str = "Function";
#if 0
		Timer time(str);
#else
		//const char* s = "Function1";
		//Timer time("Function1");
		Timer time(str);
#endif
		for (int i = 0; i < 1000;i ++) {
			std::cout << i << "\n";
		}
	}//计时		
	std::cout << a << "次内存分配\n";
}

内存分配:

        

左值右值:

       左值就是可存储的变量,右值就是临时变量,左值引用只能引用左值,右值引用只能引用右值

        很多const常量类型的引用,是因为兼容临时的右值和已存在的左值变量。两个&&就可以表示右值引用,但是此时不能引用左值。

不知道是什么,先存着:

        

#include <iostream>
#include <thread>
#include <chrono>
#include <algorithm>
#include <vector>
#include <tuple>
#include	<string.h>
#include <optional>
#include <variant>
//std::optional<int >a;
	//int b=a.value_or(100);//如果没有值,就使用括号里的默认值
	如果没有值。。可以通过这个来判断文件是否打开
	!a或者a.hasvalue()也可以
	//if (!a) {
	//	return;
	//}
	std::variant<std::string, int> m;//包含多种类型
	//a = 2;
	m = "hys";
	std::cout << std::get<std::string>(m)<<"\n";
	//int v = std:get<1>(m);
	//auto a = m.index();//返回索引,是第几个类型的数据
	//std::cout << a << "\n";
	auto mm = std::get_if<std::string>(&m);//返回指针类型的数据
	std::cout << *mm << "\n";

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值