C/Cpp 输出语句的变迁
C语言 printf 这是C语言提供的输出语句,示例如下 1234567#include <stdio.h>int main() { int number = 42; printf("The answer is %d.\n", number); return 0;} printf支持不定参数,占位符采用%d等,常见类型的占位符如下: %c:字符 %s:字符串 %d或%i:(十进制)int %ld:(十进制)`long int`` %lld:(十进制)long long int %u:(十进制)unsigned int(同理还有%lu和%llu) %f:浮点数(包括float和double),可以控制输出的小数部分长度,例如%.12f %e:(科学记数法)浮点数(包括float和double) %p:指针值,即内存地址(十六进制) 注: %f和%lf在printf中是完全等价的,因为它将浮点数自动提升为double统一处理,但是在scanf中它们并不等价,必须进行匹配(%f对应float,%lf对应doub...
C语言 结构体笔记
关于C语言中的结构体的语法,这是C++自定义类型的基础。 C++的类提供了非常丰富的功能,但是也会尽量兼容C语言的结构体。 简单示例 下面是针对学生信息的结构体的使用示例,包括了结构体对象的定义和读写,以及通过指针传递给函数。 123456789101112131415161718192021222324252627282930313233343536373839#include <stdio.h>#include <string.h>struct Person { char name[50]; int age; double height;};void show(struct Person *person) { printf("Name: %s\n", person->name); printf("Age: %d\n", person->age); printf("Height: %.2f\n", person->...
Cpp new/delete 语句
new和delete是C++中最基础且重要的申请和释放堆内存的语法,以它为线索可以引出C++堆内存管理的相关内容,它也是智能指针所需要的基础知识,值得整理一下。 基本使用 内置类型 new和delete最基础的用法是被设计用来替代C语言中的malloc和free的,分配堆内存来构造指定类型的对象,返回对应类型的指针,例如 12int *p = new int; // uninitializeddelete p; 这两个语句都是非常危险的: 最简单的new语句对内置数据类型不会对内存进行初始化,我们得到值是随机的;(Debug模式下可能内存被清空,改成Release模式可以看到随机值) delete语句不会在释放内存之后将指针置空,指针会变成空悬指针。 我们可以用下面的new语句进行初始化 12345double *p1 = new double; // uninitializeddouble *p2 = new double(); // 0double *p3 = new double{}; // 0double *p4 = n...
Cpp 显式加载动态库
在 C++ 中,加载动态库通常有两种方式:显式加载和隐式加载。 隐式加载比较简单,只需要设置链接选项即可自动进行,本文主要学习一下显式加载。 概述 在 C++ 中,显式加载和隐式加载动态库的基本介绍如下: 隐式加载是最常用的方式,通常的C++标准库等系统中的库都是采用隐式加载的,程序在编译时需要添加-l选项链接到动态库。在程序启用时,系统会自动查找并加载对应的动态库。 隐式加载的优势是代码简单,不需要在代码中处理加载动态库的各种细节。但缺点是要求在编译时动态库也要参与链接,在编译时和运行时都需要保证动态库是可以找到并且使用的,编译时无法找到则编译失败,运行时无法找到和使用则程序无法启动。 显式加载则是一种更灵活的方式,我们可以在代码中精准地控制加载和卸载动态库的所有细节。 优势是程序在编译链接时完全不需要动态库的参与,程序在运行时可以根据需要有选择性地进行加载或卸载动态库,即使在运行时对某个动态库的加载失败也不会导致程序中止。 基于显式加载我们可以实现动态库的热更新:在使用动态库的程序保持运行的情况下,更新对应的动态库。 这对于一些大型的商业服务是非常普遍的需求,他们不希望因...
Cpp 奇异递归模板模式 CRTP
CRTP 是一个非常经典的C++静态多态实现方案,在很多库中都有应用,这里整理一下。 概述 奇异递归模板模式(Curiously Recurring Template Pattern, CRTP)是 C++ 编程中一种常用的静态多态方案,它通过模板继承和静态绑定来实现类型多态。 相比于C++直接提供的基于虚函数的动态多态方案,CRTP既可以避免虚函数的额外性能开销,还可以在编译时获得更好的类型检查和优化。 CRTP的核心思想如下: 模板基类是一个模板类,模板参数是派生类自身。(这种递归的模板参数是这个模式名字的来源) 派生类:派生类继承自通过自身类型实例化的基类。 通过基类调用方法时,首先会将this指针进行类型转换为派生类的类型(因为派生类类型是模板参数,基类可以获取到派生类类型),然后就可以调用派生类的方法。 这种处理导致我们无法添加基类自身的方法实现并使用,因此基类只能作为一个抽象基类使用。 这一切都发生在模板实例化的过程中,在编译期中即可完全确定。 CRTP具有如下的优势: 静态多态:CRTP的是静态多态,通过基类调用派生类的方法是静态绑定的,完全在编译期通过模板实...
Cpp 值封装类型学习笔记
现代C++提供了几个非常实用的值封装类型工具,分别为 std::optional<T> 可能含有T类型的值或者无值;(C++17引入) std::variant<T1,T2,...> (类型安全的union)可能含有类型列表中某个类型的值;(正常情况下不会无值)(C++17引入) std::any 可能含有任意类型的值,也可能无值,使用者必须提供自行指定合理的类型;(如果内部的值转换失败会抛异常)(C++17引入) std::expected<T,E> 可能含有正常的T类型的结果,也可能含有异常的E类型的值;(C++23引入) 它们的定位和用法比较类似,因此一起整理一下,因为这几个工具的语法仍然在迅速发展中,本文只考虑C++20和C++23已经支持的语法。 需要注意的是,这些类型工具都只能接收简单的类型,不允许使用数组类型和引用类型,并且最好不要含有cv修饰符。 std::optional std::optional<T>对象的值可能是T对象,也可能是std::nullopt(代表没有值),注意这里的T不允许是数组类型和引用类型...
Cpp static+extern+inline 学习笔记
整理一下关于C++中的几个关键词(static,extern和inline)的笔记。 static static这个关键词的语义非常复杂,在C语言中本来就有多重语义,C++又添加了额外的语义。 静态函数 默认情况下,函数可以被其它编译单元使用,具有外部链接性,可以使用static将其改为具有内部链接性的静态函数,只有在当前文件中才可以使用此函数。 例如 123static void func();void func(){} 静态全局变量 与静态函数类似,全局变量在默认情况下可以被其它编译单元所使用,具有外部链接性, 可以使用static将其改为静态全局变量,只有在当前文件中才可以使用。 例如 1static int s = 100; 静态局部变量 在函数体内部使用static修饰的局部变量会变成静态局部变量,此时静态的语义不再是对外部不可见,而是延长生命周期,此时变量的存储位置从栈区转移到了数据区,不会因为函数调用的结束而销毁,下次进入函数体时会自动忽略定义和初始化语句,例如 12345int func(){ static int s = 0;...
Cpp 面向对象——访问和继承权限
在 C++ 的面向对象编程中,有三个访问权限限定词:public、private 和 protected,用于定义对类成员(成员包括变量和函数)的可访问性, 也包括在继承后的访问权限变化。 我们主要对class进行讨论,最后会讨论struct和class的区别。 访问权限 C++ 通过访问权限限定词来提供数据封装和信息隐藏机制,这有助于提高代码的可读性和可维护性,三种访问权限限定词的基本语义如下: public成员:公开的类成员,可以通过类的成员函数和类的对象访问。 protected成员:受保护的类成员,只能通过类的成员函数访问,无法通过类的对象访问。 private成员:私有的类成员,只能通过类的成员函数访问,无法通过类的对象访问。 注意这里访问的含义:对数据成员的访问是读写,对成员函数的访问则是函数调用。 对于数据的访问权限并没有被进一步拆分为只读和可读写,只读的访问效果需要基于const实现,实现只写的访问效果则需要考虑左值和右值特性,不在本文的讨论范围。 通过类的对象访问是指a.x和a.show()这类语法。 通过类的成员函数访问是指在成员函数体内部对类的其它成员访...
Cpp 面向对象——虚函数进阶
前面简单介绍了重定义和重写,以及重写所涉及到的虚函数的基本概念,这里整理一下虚函数和动态多态的进阶内容。 C++使用虚函数机制来实现运行期的多态(动态多态),与之相对的是编译期的多态(静态多态), 对于C++来说,静态多态可以通过函数重载和泛型编程实现,而动态多态则通过虚函数实现。 在讨论的范围内,基类或派生类指针/引用的使用方法通常是完全等效的,示例主要以指针的使用为主。 为了简化讨论,我们不关注public,private,protected修饰的区别,无论是对类的方法还是继承关系的修饰,统一使用public。 我们不关注多继承、菱形继承以及虚继承等复杂情景,只考虑简单的单继承情景,不考虑模板类的处理。 虚析构函数 对于含有虚函数的类,非常建议将基类的析构函数也设置为虚函数,此时派生类的析构函数也全部会自动变为虚函数,这可以保证基类指针指向派生类时, 在销毁时可以正确调用派生类的析构函数。 考虑下面的例子 123456789101112131415161718192021222324252627282930#include <iostream>struct Bas...
Cpp 面向对象——重定义与重写、虚函数
在一个作用域中,同名函数但是形参列表不同的函数构成重载的关系, 在考虑面向对象时,则会产生一系列新的问题,因为基类和派生类的作用域是一种非常微妙的关系,既不能说它们是简单的两个独立的作用域,也不能说是一个作用域,而且这种关系是天然不对称的。 在面向对象的语法中对基类和派生类的同名函数(相同或者不同的形参列表)都有着特殊的设计,在本文中我们主要讨论的是不涉及虚函数的部分。 为了简化讨论,我们不关注public,private,protected修饰的区别,无论是对类的方法还是继承关系的修饰,统一使用public。 我们不关注多继承、菱形继承以及虚继承等复杂情景,只考虑简单的单继承情景,不考虑模板类的处理。 重定义(隐藏) 例子 假设基类Base定义了一个方法hello,有好几个版本,相互之间构成重载关系。 派生类Derived如果没有实现hello方法,那么派生类对象仍然是可以直接调用hello方法的,包括基类所实现的各种版本,通过重载决议调用,这是继承所赋予的特点。 例如 12345678910111213141516#include <iostream>struct...