c++学习

news/2024/5/14 18:47:20/文章来源:https://blog.csdn.net/m0_52785249/article/details/127185034

C++学习

  • `Static`
  • ```变量生存期和作用域```
  • ```静态局部变量```
  • ```类的继承```
  • ```多态```
  • ```虚函数```
  • ```纯虚函数(接口)```
  • ```可见性```
  • ```数组```
  • ```字符串```
  • ```const```
  • ```mutable```
  • ```成员初始化列表```
  • ```三元操作符```
  • ```在堆、栈上创建C++实例化对象```
  • ```C++运算符和其重载```
  • ```this```
  • ```C++对象的生存期```
  • 智能指针
    • ```uniqueptr(作用域指针)```
    • ```shareptr共享指针```
  • ```拷贝和拷贝构造函数```
  • ```箭头操作符```
  • ```动态数组vector```
  • ```struct```

Static

  • 类外的 static 表示在链接时,当前变量只在当前翻译单元出现。
    • 例:在其他cpp定义 static变量a,不会与主函数的cpp(或另一个cpp文件)中出现的全局变量a(未加static)冲突。
      • 而如果未加static,主函数里的变量就必须加 extern 关键字,声明当前变量是调用其他cpp的。
  • 如果是类内的static变量,一个实例化对象改变了其值,其他所有对象的值都会改变。
    • 如果要调用类内的 static 变量需要提前声明。
      • 例:必须先以 C_robot::x 的方式声明 C_robot 类内的 static变量 x。
        • 通过上面方式声明后,C_robot 所有的实例化对象才可以调用 x。
          • 可以像普通变量一样定义,也可以用C_robot::x的形式直接赋值。

变量生存期和作用域

  • 生存期:变量实际存在的时间。
  • 作用域:变量访问的范围。
  • 栈的生命周期仅在函数或类局部;堆则是在全局。
    • 作用域结束,栈上的所有东西都会被释放(内存等)

静态局部变量

  • 变量生存周期是整个程序存在的时间。
  • 在函数内部用static定义的变量和在函数外用static定义的变量意义一样。(个人把这个也理解为全局变量)
  • 可以像下图这样在类内构造单例实例,以此来调用静态函数get(其生命周期是整个函数存在的时间)
    在这里插入图片描述

类的继承

  • 继承允许我们有一个相互关联的类的层次结构

多态

  • 一个单一类型,但拥有多个类型(子类拥有父类的一切public类型)

虚函数

  • 父类需要加virtual; 子类需要加 override
  • 虚函数需要两项额外的开销,一项是父类需要指向一个表;另一个在调用的时候需要额外的性能损耗。<但不会太大>

纯虚函数(接口)

  • 纯虚函数如果有子类继承就必须重写父类的函数,否则没法实例化

可见性

  • 友元可以访问私有属性(private)
  • protected 只有子类可以访问
  • 但上述两种都只能在类中访问,无法通过实例化对象访问
  • 通过public继承,不改变可变性
  • 通过private继承,所有成员都变为private
  • 通过protected 继承,所有成员都变为protected

数组

  • 数组实际上也是一个指针
  • 如果直接对一个指针加减,实际上是做一个指针的偏移,偏移的多少也取决于类型
  • 数组也可以通过new在堆上创建,如下例:
    • 但这种方式属于间接寻址,每次调用需要先找到实体位置,再找到数组的位置,因此尽量少用
main
{
int* a[6]; //在栈上创建
int* b = new int[6]; //在堆上创建
}
  • 由于数组一般是在栈上建立的,因此,没法直接获取其数组大小,应通过下述方式获得原始数组元素数量:
main
{
int a[6]; 
int count = sizeof(a) / sizeof(int);
}
  • 如果是C++11,可以直接用array来定义数组,获取大小可以直接使用.size函数:(但会因为边界检查,会有额外开销,所以开始原始数组快一些)
#include <array>
#include <iostream>
main
{
std::array<int,6> a; //创建数组
a.size();  //获取数组大小
}

字符串

注意:在字符串前加R代表忽略转义字符

  • 字符串可以理解为字符数组。
  • 双引号一般默认是字符串(const char*),在下例中,第一行指令是无法实现的,因为是const char*类型;但是可以通过第二行指令实现,因为这里的 += 是重载了的,所以可以这么用。
#include <array>
#include <string>
main
{
std::string a = "ljc" + "coder"; //报错
std::string a = std::string("ljc") + "coder"; //顺利执行
std::string a = "ljc" "coder"; //顺利执行
std::string a = "ljc";
a += "coder";                   //顺利执行
}
  • 为什么字符串会自动终止呢,实际上就是识别到0(不加引号的0)[终止标识符],就自动终止了。(\0)
  • C++中一般使用std::string模板类来表示字符串,其实际就是一个字符数组。
    • sting有一个构造函数可以接受 char* 或 const char* 类型的数据。
    • 如果要打印 string类型数据,必须加头文件 #define <string>,否则无法用cout输出打印string数据。
  • 复制字符串是比较耗时的,在函数变量如果直接用就会在栈上复制一个新的字符串,这是很不友好的,因此,对于只读的字符串我们通常使用 const +&的方式作为参数,如下例:
#include <array>
#include <string>
void example(std::string string) //不应该这么使用
void example(const std::string& string) //应该这么使用
  • 使用string时,我们在字符串前加一个大写的L,意在表示宽字符串;小写u表示char16;大写U表示char32
    • 一般两字节在windows上使用;四字节在linux(mac)上使用
#include <array>
#include <string>
#include<stdlib.h>
main
{const char* name = "ljc"; //utf8
const wchar_t* name = L"ljc"; //2字节的字符
const char16_t* name = u"ljc"; //2字节16byte的字符 utf16
const char32_t* name = U"ljc"; //4字节32byte的字符 utf32
}

const

  • const int* 和 int const*代表的意义是一样的,关键在于 * 的位置。
  • 把const放在 * 后,变量名前: 让指针本身成为常量,不让指针分配其他指向。(但可以改变当前指针指向的内容)
  • 把const放在 * 前: 不能改变指向的内容,但可以让指针分配其他指向。
  • 在类中,放在方法名后面: 该类不能改变类的属性。(只能读取)
int* const a = new int; //不能改变指向,但可以改变指向的内容
const int* const a = new int; //不能改变指向,也不可以改变指向的内容
  • const 类只能调用 const 的方法
  • mutable函数允许const函数修改变量

mutable

  • <常用场景>
    使标记的变量可以在const的函数内使用(const类中的方法可以修改该变量)
  • <一般场景>
    修饰lambda表达式,值捕获时可以直接操作传入参数。(并非引用捕获,依旧值捕获,不修改原值)

成员初始化列表

  • 如下例所示,可以直接在类内(构造函数花括号上面)对参数初始化,这样在构造函数中就可以对函数名等进行初始化,使代码更易懂。
    • 但是要注意,初始化的顺序一定要和定义的顺序一致。
class Example
{
private :int m_core;std::string m_name; 
public:Example():m_core(0),m_name("Unknown"){}
}
  • 无论如何应该使用成员初始化列表。
    • 一方面是代码简洁性。
    • 另一方面,其也会提高对代码的执行效率,如果不使用会造成性能的浪费。
      • 事实上我们如果在构造函数再建立一个构造函数,如果不用成员初始化列表,会生成两个对象(先调用一遍默认构造函数,这会造成性能的浪费)

三元操作符

if (m_level > 5)
m_level = 10;
else
m_level = 5;
//上式等价于下式三元操作符的
m_level > 5 ? 10 : 5

在堆、栈上创建C++实例化对象

  • 我们一般都是在栈上创建C++实例化对象,但也可以用关键字 new 在堆上创建。
    • 在堆上创建会耗时一些,并且需要我们手动将其内存删除。
    • 一般是对象太大或者希望显式的控制对象的生存期,会采用在堆上创建,否则一般都在栈上创建。
class Example
{}
main
{Example* e = new Example(); //堆上创建delete e; //用完后要记得delete掉new创建的内存
}

注:new实际和c中的malloc功能差不多

C++运算符和其重载

  • 下例是对加运算符重载的例子
    • 以及左移,<< 运算符的重载例子
class Example
{
float x, float y
Example (float x, float y)x(x),y(y) {}
Example ADD(const Example& other) const
{
return Example(x+other.x,y+other.y)}
Example ADD1(const Example& other) const
{
return operator+(other)//另一种写法
}
Example ADD2(const Example& other) const
{
return *this +other; //另一种写法
}
Example operator+(const Example& other) const //重载加运算符
{
return ADD(other)}
}
std::ostream& operator<<(std::ostream& stream, const Example& other) 
{stream<<other.x<<','<<other.y;
}
main()
{Example speed(1.1f,2.1f);Example position(0.5f,1.5f);Example result1 = position.ADD(speed);  // 普通Example result2 = position + speed; // 重载加运算符std::cout << result2 << std::endl; // 使用重载的<<运算符直接打印result2 的结果
}

this

  • this 是指向当前对象实例的指针,该方法属于对象实例

C++对象的生存期

  • 如下例所示,在花括号内实例化的e 对象实际上是在栈上建立的,花括号就是其作用域,在作用域结束后,会销毁栈上的所有东西,也就是说这时候就会调用类的析构函数。
    • 而e1是通过new关键字在堆上建立的,如果不delete,只有当应用程序整体结束才会自动清理其内存。
class Example
{}main()
{{ Example e; //在栈上建立对象eExample *e1 = new Example();  // 在堆上建立对象e1
}}

智能指针

uniqueptr(作用域指针)

  • 作用域指针可以在构造时用堆分配指针,在析构时删除指针。
    • 下例是自己构造的一个作用域指针
class Example
{}
class Scopedptr
{
private :Example* m_ptr;
public:Scopedptr(Example* ptr):m_ptr(ptr){}~Scopedptr(){delete m_ptr;}
}
main()
{
Example* e = new Example(); // 没用作用域指针之前得这么在堆上建立对象,后面还需要自己delete
Scopedptr e = new Example(); // 使用作用域指针建立对象
Scopedptr e(new Example()); // 使用作用域指针建立对象的另一种写法
}
  • 沿用上例,可以直接调用unique_ptr的标准方法,出了花括号的作用域后,会自动析构当前对象(delete指针)
  • 但是unique_ptr创建的对象不能被其他指针指向,因为如果当前对象被自动析构了,第二个指针就会指向空,而share_ptr则不会,因为它是通过引用计数来跟踪指针有多少引用的。
class Example
{  }
main()
{
{std::unique_ptr<Example> e (new Example()) //一种使用作用域指针的方法(圆括号内是调用构造函数,因为unique_ptr是需要显式调用构造函数的),但是这种构造函数的调用方法并不安全std::unique_ptr<Example> e = std::make_unique<Example>(); // 一般使用这种方式调用构造函数(异常安全)
}
}

shareptr共享指针

  • 像上面说的,每次建立一次shareptr的指针,引用计数加一,而删除一个对应的指针后,引用计数减一,直到引用计数为零,彻底释放指针
  • 沿用上例
class Example
{  }
main()
{
{std::share_ptr<Example> e = std::make_shared<Example>();std::share_ptr<Example> e0 = e;
}
}
  • 或者像这里这样,创造两个作用域,在里面那个花括号里的作用域内有e,在出了里面这个花括号的作用域后,e0还是存在的。
class Example
{  }
main()
{std::share_ptr<Example> e0 ;{std::share_ptr<Example> e = std::make_shared<Example>();	e0 = e; }
}
  • 注意:当你声明一个堆分配的对象,又不想自己清理,可以使用智能指针,并且优先使用 unique_ptr ,因为其具有一个较低的开销。
    • 但是,如果需要在对象之间共享时,使用share_ptr

拷贝和拷贝构造函数

  • 浅拷贝:用等号来赋值的语句,相当于只是复制了值,会占用两份内存
  • 深拷贝:根据定义复制整个对象。
    • 深拷贝出来clone之外,还可以通过拷贝构造函数完成。
  • 拷贝构造函数是一个构造函数,当你复制第二个字符串时,他会被调用。
    • 当你试图把字符串赋值给一个对象时,这个对象也是一个字符串。当你试图给他创建一个新的变量,并给他分配另一个变量时,这个变量和你正在创建的变量有相同的类型,而你复制这个变量,这就是拷贝构造函数。
class Example 
{}
Example* a = new Example();
Example*b = a;
b++; //这不会影响到a指针
b->3; //这会影响到a指针,因为他们指向同一个内存地址
  • 自己初始化一个string类
    • 在下例中会直接报错,因为在main函数中,实例化了两个对象,但他们实际上都是指向一个地址的,但在结束之后,会delete两次内存,所以会出现问题。
  • 下面的例子一定要仔细看,有注释很关键
class String
{
private:char* m_buffer;unsigned int m_size;
public:String(const char* string){m_size = strlen(string);m_buffer = new char [m_size + 1]; //这里+1是给转义字符留一个位置memcpy(m_buffer,string,m_size);m_buffer[m_size] = 0; //手动增加空终止符 (\0)	}String(const String& other)//c++默认的拷贝构造函数,作用是将other浅拷贝给成员变量:m_buffer(other.m_buffer),m_size(other.m_size){}//下面是深拷贝的拷贝构造函数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)}//或者将来拷贝构造函数写成下面这种String(const String& other){memcpy(this,&other,sizeof(String))}//如果不要拷贝构造函数,可以使用下面的写法,禁止拷贝构造函数,事实上,unique_pyr也正是这么操作的String(const String& other) = delete~ String(){delete[] m_buffer;}friend std::ostream& operator<<(std:ostream& stream,const String& string);
}
std::ostream& operator<<(std:ostream& stream,const String& string)
{stream << string.m_buffer;return stream
}
int main()
{String a = "abc";String b = "opq";std::cout << a << std::endl;std::cout << b << std::endl;}
  • 上面我们可以看到,拷贝构造函数是在堆上创建的内存,而我们在函数调用时,很多时候不需要这样做,所以,可以在写参数时,用const引用对象的方式去传递对象,这样就不会调用拷贝构造函数,能很好的节省内存消耗。
  • 记住,我们常用const引用来传递对象
  • 例:
void printstr(const Example& e) //Example是上例中的一个类,当然也可以是标准类,如int、string等
{Example e1 = e;
}

箭头操作符

  • 例子:
    • 如果我们用指针调用对象的方法,使用箭头可以很好的帮我们简化代码
#include <iostream>
class Example
{void printf1();
}
main()
{Example a;*b = &a;b.printf1();  //会报错,因为 b 只是一个指针,不是对象,无法调用方法//可以通过下面的例子达到目的Example& c = *b;c.printf1();//但上面太冗余了,可以直接用箭头来表示b->printf1();
}

动态数组vector

  • 原理上来说,会先创建有10个元素的内存,如果你的输入超过了10个元素,会创建一个新的内存,把之前的元素复制过来,并删除原来的内存。
  • 总的来说标准模板库速度都会慢一些,如果需要速度,最好是自己重写一个模板库,当然,也可以通过使用emplace_back这种方式来优化。
  • 例子 (注意看里面的注释)
struct speed
{int x,y,z;
}
std::ostream operator<<(const ostream& stream, const speed& sp)
{stream << sp.x << "," << sp.y << "," << sp.z;
}int main()
{std::vector<speed> sp ;sp.push_buck({1,2,3});sp.push_back({4,5,6});//遍历打印for (int i = 0, i < sp.size(), i++){std::cout << sp[i] << std::endl;}for (const speed& s: sp){std::cout << s << std::endl;}//删除vector内某一项通过earse实现(借助迭代器)sp.erase(sp.begin()+1); //删除容器中的第二个元素//vector的优化使用方法//第一种:指定vector大小(这种方式仍然需要在main中构造再复制到vector中)sp.reserve(3) //创建大小为3的容器//第二种:(我们一般使用的方法)emplace_backsp.emplace_buck(1,2,3);//直接构造struct对象的参数传入vector而不需要复制sp.emplace_back(4,5,6);}

struct

  • struct 往往被我们用于封装一个自己定义的类型空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_21929.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu安装微信

1.安装wine sudo dpkg --add-architecture i386 sudo mkdir -pm755 /etc/apt/keyrings sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key//根据你的系统执行不同的命令 Ubuntu 22.04 sudo wget -NP /etc/apt/sources.list.d/…

快乐刷课---Tampermonkey下载使用

TampermonkeyChrome插件伴侣下载资源&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1IIzB8N2iPW2RjUO2pqDVHw?pwd6666 提取码&#xff1a;6666 1. 下载 Tampermonkey 进入油猴的官网Tampermonkey&#xff0c;下载你使用的浏览器对应的版本 以谷歌浏览器为例&am…

如何计算维吉尼亚密码?Java实现维吉尼亚密码的加密解密算法

文章目录如何计算维吉尼亚密码&#xff1f;Java实现加密算法Java实现解密算法参考博客如何计算维吉尼亚密码&#xff1f; 计算维吉尼亚密码有2种方式&#xff0c;一种是根据密码表查找&#xff0c;另一种是手动计算方法。 1.密码表查找法 第一行是密钥&#xff0c;第一列是明文…

CH579 Cortex-M0 内核低功耗蓝牙 MCU 集成 ARM 内核 32 位微控制器

概述 CH579 是集成 BLE 无线通讯的 ARM 内核 32 位微控制器。片上集成低功耗蓝牙 BLE 通讯模块、以太网控制器及收发器、全速 USB 主机和设备控制器及收发器、段式 LCD 驱动模块、ADC、触摸按键检测模块、RTC 等丰富的外设资源。 特点 32 位 ARM Cortex-M0 内核&#xff0c;…

Arduino常用函数(二)

数学函数 1、min(x,y)函数的作用是返回x&#xff0c;y两者中较小的。 2、max(x,y)函数的作用是返回x&#xff0c;y两者中较大的。 3、abs(x)函数的作用是获取x的绝对值。 4、constrain(amt,low,high)函数的工作过程是&#xff0c;如果amt小于low&#xff0c;则返回low&…

Pytho07--面向对象2

之前我们已经知道了面向对象的概念及在python中创建空类&#xff0c;带方法的类&#xff0c;带初始化方法的类&#xff0c;带实例化方法的类等并认识了类的成员。在我们将其与Java的代码进行对比后发现了python确实有它的方便之处。面向对象的内容不止之前文章中提到的那些&…

IDEA+Tomcat——前端输入数据乱码问题

IDEATomcat——前端输入数据乱码问题 给别人远程部署项目的时候&#xff0c;发现比较老的项目会出现接收前端数据是乱码的问题&#xff0c;但这个项目在我自己的电脑上却是正常的&#xff0c;通过对比发现&#xff0c;IDEA版本或Tomcat版本不同及过低是造成此问题的主要原因&am…

【数学与算法】最小生成树Spanning Trees

链接 无向图&#xff1a; 无向图的意思是&#xff0c;边没有方向。 树&#xff1a; 树是一类特殊的图&#xff0c;树是由节点和无向边构成的&#xff1b; 所有的树都是无向图&#xff0c;但是无向图未必是树&#xff1b; 树有一些性质&#xff0c;但并非所有图都有这些性质…

webrtc防抖动策略NetEq

什么是NetEq:进行抖动控制和丢包隐藏,让音频更平滑。 NetEq的位置 消除抖动的基本原理 NetEq整体架构 NetEq用到的几种缓冲区 NetEq的MCU与DSP NetEq的位置: 网络抖动的计算方式: 两个包在发送端的时间间隔为S,在接收端的间隔为R,那么抖动为J=S-R。 NetEq缓冲区设置多…

golang中struct

前面已经介绍的数组&#xff0c;slice,map有一定的相同之处&#xff0c;即处理的都是相同类型的元素&#xff0c;map中的key和value属于相同的类型&#xff0c;但如果要把多个类型的元素放到一起进行处理&#xff0c;则要使用go语言为我们提供的数据结构struct struct非常适合定…

【Arcgis操作】模块化(批量、自动化)计算多个图层的面积

有很多个图层的面积要计算&#xff0c;如果采用普通的方法&#xff0c;需要给每个图层添加【字段】&#xff0c;然后再挨个计算&#xff0c;图层少的话还好&#xff0c;图层太多的话&#xff0c;很麻烦&#xff0c;很累。 那么&#xff0c;有没有一种方法&#xff0c;能够批量…

OpenCV-Python学习(7)—— OpenCV 轨迹栏操作和键盘响应操作

1. 知识点 cv.namedWindow() 创建一个窗口&#xff1b;cv.createTrackbar() 创建一个轨迹栏&#xff1b;cv.getTrackbarPos() 获取对应轨迹栏的轨迹位置&#xff1b;cv.waitKey() 键盘操作返回对应的key。 2. cv.namedWindow() 函数说明 函数使用 cv.namedWindow(winname, …

【每日算法题】最后一个单词的长度(简单)

今天开始学一学算法✨&#xff0c;前两天研究了下算法&#xff0c;发现算法和数据结构是程序的灵魂&#xff0c;这句话可真没错。 今天先从简单的开始吧&#x1f601;&#xff0c;LeetCode 第 58 题&#xff1a;最后一个单词的长度 题目&#xff1a;给你一个字符串 s&#xf…

Linux下使用WPS做office的二次开发

Linux下使用WPS做office的二次开发 序 上个版本WPS在Linux上就已经支持二次开发了&#xff0c;可以直接去看官网相关的介绍。https://open.wps.cn/ 我们选择WPS的客户端进行二次开发 开发环境 Ubuntu18.04wps-office_11.1.0.9126_amd64.debQt的开发环境&#xff08;我本地…

REACT全家桶(1)

基础 一、特点 声明式设计 高效 减少dom操作 灵活 JSX JS拓展语法 组件 单向响应的数据流 二、虚拟DOM 把真实DOM树转成对象树&#xff0c;再通过diff算法&#xff0c;减少重绘与回流 三、搭建环境&#xff08;提前安装node环境&#xff09; 1.全局安装create-rea…

《图解 HTTP 》阅读笔记(三)

书接上文《图解 HTTP 》阅读笔记&#xff08;二&#xff09;&#xff0c;我们继续探索总结http的相关知识点。 我们还是先把问题摆到台面&#xff0c;带着问题读文章。 6.第九章&第十章 知识点 6.1 HTTP的瓶颈以及相应解决方案 HTTP优缺点分析的时候&#xff0c;我们是相对…

ELK+Filebead+zookeeper+kafka部署

目录 一、为什么要做日志分析平台&#xff1f; 二、ELKFilebeatKafkaZookeeper架构 三、搭建ELKFilebeatKafkaZookeeper 1、3台机子安装zookeeper 192.168.100.14/15/16 1.1 解压安装zookeeper软件包 1.2 修改Zookeeper配置配置文件 1.3 设置myid号以及启动脚本 1.4 3…

【前端修炼场】— 网页到底是添加超链接的呢

此文为【前端修炼场】第六篇&#xff0c;上一篇文章链接&#xff1a;img 标签 文章目录前言一、超链接引入二、属性值介绍2.1 href 属性值2.1.1 在同一文件路径下跳转2.1.2 跳转任意网址2.2 title 属性值2.3 target 属性值总结前言 本篇文章我将带领诸位学习如何在页面中添加超…

GitHub 供应链安全已支持 Dart 开发者生态

通过 Dart 和 GitHub 团队的共同努力&#xff0c;自 10 月 7 日起&#xff0c;GitHub 的 Advisory Database (安全咨询数据库)、Dependency Graph (依赖项关系图) 和 Dependabot (依赖更新机器人) 开始支持 Dart 开发者生态&#xff0c;这也意味着 GitHub 为 Dart 和 Flutter 应…

单商户商城系统功能拆解13—分类管理

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…