语言探幽
语言探幽
前言:完整的总结总是劳神耗时的,已经熟稔的或者易于得到的部分也没有总结下来的必要。然遨游码海,总归有一些需要记忆的小点,好记性不如烂笔头,单开一篇又没必要,索性全归为一篇,作为手册。而本文正是码海拾遗姊妹篇——语言探幽。主要记载一些在编程语言中碰到的小知识小技巧。
1 C
1.1 语法&特性
宏
- 宏的拼接:
#
拼接为字符串,##
拼接为一个整体 ⭐##连接符和#符的使用 ⭐字符串化和合并操作符 - 宏的一次执行:
#define FUNC(EXP) do{xxx} while(0)
,用 do-while(0) 可以避免一些bug
1.2 函数速查
(可以参考本地文件[C函数速查](file://D:/books/C/C函数速查.pdf))
⭐笔试输入输出
/* (1) 类型混排
* push 123
* pop
* front
*/
scanf("%s %d", &str, &x);
// %s 是以空白符为界定的,因此不用担心会读取一行
/* (2) 读取一行
*/
scanf("%[^\n]", &line); // scanf 可以进行简单的模式匹配
getline(&line, &size, stdin); // posix 的函数,可以自动分配大小
fgets(line, size, stdin); // 读取指定数目或一行
printf
-
打印颜色
颜色控制码-cnblogs// 一般格式为 printf("\033[控制码1;控制码2;控制码3...m %s \033[控制码m\n", str); // 例如下面将输出一个黄色的str printf("\033[32m %s \033[0m \n", str);
-
打印数据类型
维基百科-printf-format 参考-runoob.com
a A
:以十六进制形式输出浮点数(C99)z
:输出size_t
类型的推荐做法 -
位宽限定符:
%[width]d
:限定整数的最小位宽,可以用数字替代[width]
,如%5d
表示整数占5位宽度。%[width].[precision]f
:限定浮点数的位宽和小数点后的位数,可以用数字替代[width]
和[precision]
,如%8.2f
表示浮点数占8位宽度,小数点后保留2位。%[width]s
:限定字符串的最小位宽,可以用数字替代[width]
,如%10s
表示字符串占10位宽度。
-
填充限定符:
%-[width]d
:左对齐限定整数的最小位宽,可以用数字替代[width]
,如%-5d
表示整数左对齐并占5位宽度。%0[width]d
:用零填充限定整数的最小位宽,可以用数字替代[width]
,如%04d
表示整数用零填充并占4位宽度。
-
变参限定符:
%*c
:动态指定字符的宽度,可以通过传递参数给printf()
函数来指定字符的宽度,如printf("%*c", 5, '*')
表示打印一个宽度为 5 的字符*
。%.*s
:动态指定字符串的宽度,可以通过传递参数给printf()
函数来指定字符串的宽度,如printf("%.*s", 8, "Hello, World!")
表示打印字符串的前 8 个字符。
-
其他特殊格式化方法:
%n
:将已经打印的字符数保存到对应的int
变量中,例如printf("Hello, %s%n", "World!", &count)
,count
将保存字符串 "Hello, World!" 的字符数。%m
:输出上一个发生的错误消息(通常与错误处理相关)。%p
:打印指针的地址。
sprintf & vsprintf
二者都能格式化输出到字符串,区别在于,v
系列能“定制”,你可以将其封装到你的函数中去,以应对参数不确定的情况。
#include <stdio.h>
int printf(const char *format, ...); // 到标准输出
int fprintf(FILE *stream, const char *format, ...); // 到文件
int dprintf(int fd, const char *format, ...); // 到文件
int sprintf(char *str, const char *format, ...); // 到字符串
int snprintf(char *str, size_t size, const char *format, ...); // 到字符串
#include <stdarg.h>
int vprintf(const char *format, va_list ap); // 也到标准输出
int vfprintf(FILE *stream, const char *format, va_list ap); // 也到文件
int vdprintf(int fd, const char *format, va_list ap); // 也到文件
int vsprintf(char *str, const char *format, va_list ap); // 也到字符串
int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 也到字符串
字符串处理
(1)输入输出:
//(1) <stdio.h>:
// 成功返回 s,失败返回 NULL;会把换行符读进 s 里面(做字符串比较的时候要注意)
char *fgets( char *s, int size, FILE *stream);
// 成功返回 nonnegative number,失败返回 EOF;不会输出 '\0',但会输出 '\n'到 stream 里面
int fputs(const char *s, FILE *stream);
//会读取换行符进入目标; 当 lineptr 已经被 malloc,但是不够大,则会自动 relloc;当目标为空指针,且 n 为 0 的时候会自动 malloc;无论getline成功与否,都要由用户来负责 free 这块内存
ssize_t getline (char **lineptr, size_t *n, FILE *stream);
ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);
(2)处理:
// (1)查找
// 返回指向首次出现needle的指针。如strstr("hello", "ll");则返回指向第一个l的指针
char *strstr (const char *str, const char *needle);
// 返回指向首次出现字符c位置的指针
char *strchr (const char *str, int c);
// 查找 str 中,第一个不在 accept 列表中的字符位置
char *strpbrk(const char *str, const char *accept);
// 查找 str 中,开头出现 accept 中字符的个数。如str=“ABABCDE”,accept=“ABD”,则返回 4
size_t strspn (const char *s, const char *accept);
// 查找 str 中,开头 不 出现 reject 中字符的个数
size_t strcspn(const char *s, const char *reject);
// (2)比较字符串,相等返回 0
int strcmp (const char *s1, const char *s2); // 比较
int strncmp (const char *s1, const char *s2, size_t n); // 比较前 n 个字符
int strcasecmp (const char *s1, const char *s2); // 比较,但不区分大小写
int strncasecmp(const char *s1, const char *s2, size_t n); // 比较前 n 个字符且不区分大小写
// (3)以 delim 为分隔符,分割 str (一般要用到循环,即while(token != NULL) )
// 第一次调用要指明 str; 后面的每一次想分割同一个字符串的调用,都要把 str 置为 NULL
// 返回值是分割得到的子串,剩余部分是保存在内核(?)中,因此后续调用不再传递 str
char *strtok(char *str, const char *delim);
(3)格式转换:
// 字符串转为别的数据类型
#include <stdlib.h>
double atof (const char *nptr);
int atoi (const char *nptr);
long atol (const char *nptr);
long long atoll(const char *nptr);
2 C++
2.1 语法&特性
返回值-NRV
有一条语句,A a = f()
;其中f()是一个函数,函数里边申请了一个A的对象b,然后把对象b返回。在对象返回的时候,一般情况下要调用拷贝函数,把函数f()里边的局部对象b拷贝到函数外部的对象a。但
NRV:具名返回值优化(Named Return Value)简单的说就是,如果用了NRV优化,那就不必要调用拷贝构造函数,编译器可以这样做,把a的地址传递进函数f(),然后不让f()申请要返回的对象b的空间,用a的地址来代替b的地址,这样当要返回对象b的时候,就不必要拷贝了,因为b就是a,省去了局部变量b,省去了拷贝的过程。
指针传参
指针作为参数传递,也是传值,即拷贝一份指针。这同样意味着你在函数中对指针的操作,并不会真正改变原指针。这里要注意理解:不改变的,是指针指向哪里,但是你却可以通过这个指针,真实的改变原来指针指向的内容。
如果你想修改一个值,你可以传递指向它的指针(或引用)。因此如果你想修改指针的值(即指针的指向),那么你就要传递指向指针的指针(或指针的引用)。
但是要注意,如果你修改了指针的指向,那么原来指针的指向也会变,这就意味着,原来指针指向的那一块内存区域可能无法引用了,那就会造成内存泄漏。
类
(1) 语法
- 一些“关键字”:
inline
(外部函数变为内联)、const
(不修改)、default
()、explict
(不隐式类型转换)、virtual
(虚函数、虚继承)、static
(类成员/静态成员)、using
(可以在基类中隐藏父类的public继承来的东西) - 初始化列表:构造函数后以
:
开始,进行初始化。- 静态成员只能在类内定义,类外初始化,不能出现在初始化列表中(因为一个类只含有一份?)
- 基类的构造函数,也在初始化列表中调用
D(int a, int b, int c) : B(a, b), _c(c)
(如果基类没有默认构造函数,那子类必须显示调用) - 初始化顺序只看声明顺序(以及构造函数调用顺序),与初始化列表出现顺序无关。(注意区别,跟出现次序有关的是多继承时,构造函数的调用顺序)
- 子类只关心父类的初始化,不管再往上几辈的事,爷爷类自有父类负责(虚基类除外:子类还要调用虚基类的构造函数来初始化虚基类子对象,每个低层次的类都要写,但是实际上只有最派生类执行)【最派生类】:建立对象时所指定的类
- 构造与析构函数
- 构造函数调用顺序:虚基类 \(\Rarr\) 父类(按声明左到右) \(\Rarr\)客人(类类型数据,按定义前到后)\(\Rarr\) 自己。析构顺序与之相反
- 不管显示还是隐式,基类总是要调用父类的构造函数,以及析构函数的。因此可以用子类的指针来析构父类,但父类的指针不会析构子类
- 类型适应:当且仅当
- 继承不会继承基类的构造和析构函数;
- 虚继承:虚继承即让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类。目的是解决菱形继承问题\(\Rarr\)同名虚基类,在对象中只会产生一份虚基类子对象(全部父类加上子类,共享一个虚基类子对象)
(2) 内存布局
- 成员函数在代码区,不占有对象的内存。成员函数中定义的局部变量是“共享的”
- 有虚函数的,对象首块内存会存放一个指向虚表的指针。可以
(void **)(*(void**)&obj)
- 有继承的,子类中先出现的是父类中的数据成员(基类子对象),然后紧跟着是自己的数据成员
- 如果父类中有虚函数,子类也会有一个虚表,如果没有重写父类的虚函数,那虚表中项目就指向父类的虚函数,如果重写了,那就指向子类的虚函数
(3) 常见问题
- 拷贝构造函数参数:
- 深浅拷贝:
- 虚构造函数与虚析构函数:
关键字和上下文关键字
// delete
void func(int)=delete;
// override, final
void f() override final;
类模板与友元函数
// 模板类中声明友元函数共有四种:
template <typename T>
class A{
public:
friend void f1(); // 1.不需要模板参数的,非模板函数
friend void f2(A<T>& a); // 2.需要模板参数的,非模板函数
friend void f3<T>(A<T>& a); // 3.需要模板参数的,模板函数
template <typename U> // 4.需要模板参数且自带参数的模板函数
friend void f4(A<U>& a);
private:
T v;
};
不要在类内声明的是第二种,在类外定义的却是第三种了。
智能指针
Lambda 表达式
auto foo = [capture](parameters)->return_type {func_body}
-
[capture] 用来捕获表达式外部的变量:
-
[ = ] :以值(拷贝)的方式捕获所有外部变量,函数体内可以访问,但是不能修改。
-
[ & ] :以引用的方式捕获所有外部变量,函数体内可以访问并修改(需要当心无效的引用);
-
[ var ] :以值(拷贝)的方式捕获某个外部变量,函数体可以访问但不能修改。
-
[ &var ] :以引用的方式获取某个外部变量,函数体可以访问并修改
-
[ this ] :捕获this指针,可以访问类的成员变量和函数
-
[ =,&var ] :引用捕获变量var,其他外部变量使用值捕获。
-
[ &,var ] :只捕获变量var,其他外部变量使用引用捕获。
-
(parameters):即自定义函数里面的参数,只能在表达式的封闭范围内用
-
返回值可以自动推断
2.2 库与函数速查
输入输出
⭐getline()
-
有两个getline()函数,一个在<istream>里面,通过(输入流)成员调用的方式指出输入流,一个在<string>里面,为顶层函数,在参数列表中指出输入流
-
getline()实际读取的为(n - 1)个字符,且会把终止符从流中删除
-
读到文件尾返回eofbit
// # include<istream> // 注意是char*,同时要指出读入的size
istream& getline (char* s, streamsize n, char delim );
// # include<string>
istream& getline (istream& is, string& str, char delim);
⭐get()
-
与getline()区别在于,读到delim字符后,会中止并将其保留在流中,这就会被下一个用户读到
-
只有<istream>版本
// 依次为:single character (1),c-string (2),stream buffer (3)
int get(); istream& get (char& c);
istream& get (char* s, streamsize n, char delim);
istream& get (streambuf& sb, char delim);
正则表达式
<regex>库是C11中新增的特性,下面给出一些使用的例子
regex
:定义一个正则表达式类,如regex rx("^[0-9]");
match_result
:
2.3 标准库STL
数组
(1) vector
- vector的大小。里面似乎存放的只是几个指针而已,start、fin等。在使用
umap
时候发现的
string类
栈与队列
(1) priority_queue
template<
typename T, // 类型
typename Container = std::vector<T>,
typename Compare = std::less<typename Container::value_type>
> class priority_queue;
优先队列的底层实现是二叉堆,插入时,让使 Compare 返回 true 的元素下沉(sink)。
使用时容易模糊的就在于,Compare
怎么定义(讨论的是自定义类型,而非POD)。一般的策略有:在类型中重载 <
运算符,则即可缺省 compare,或定义仿函数
//(1) Comp缺省,自定义类型中,重载或友元重载小于 < 运算符
struct ListNode {
bool operator< (const ListNode&){}
friend operator< (const ListNode&, const ListNode&){}
}
//(2) Comp为仿函数(即定义一个类Comp,然后重载它的括号运算符)
Class Comp{
bool operator()(const T&, const T&){}}
常用的操作有push(), pop(), top(), empty()
集合 与 映射
(1) set
⭐cpp-reference ⭐[set博客总结](c++ stl库中的set - 然终酒肆 - 博客园 (cnblogs.com))
template<
typename Key,
typename Compare = std::less<Key>,
typename Allocator = std::allocator<Key>
> class multiset;
注意四个版本:set
, multiset
, unordered_set
, unordered_multiset
默认有序是升序,(只需使用 rbegin()
和rend()
就能的到逆序的)
常用操作:insert、find、count、clear、empty
(2) map
默认按关键字升序,STL中的有序均是升序,因此需要重载的只是小于号 \(<\)
链表
(1) list
template<
typename T,
typename Allocator = std::allocator<T>
> class list;
双向链表
front、back、push_back、pop_front、insert、erase、remove_if、sort、unique、reverse
(2) forward_list
单项链表
3 python
3.1 语法
数据结构
函数
@funcA def funcB(): ...
:其中 @funcA是函数装饰器,它有两步工作:- 将 funcB 作为参数传给 funcA() 函数,也就是说函数 A 的参数是一个函数,在A里面可以执行的
- 将 funcA() 函数执行完成的返回值反馈回 B,也就是说 B 的返回值是在A中返回的,是A以B为参数的执行结果
strip()
:去除两端多余字符串
类
class D(A, B, C) :
表示 D 继承 A B Csuper().__init__()
:super指向父类,用此句可以初始化父类成员__add__() __gt__()
:等,是操作符重载
3.2 库
prettytable
绘制 mysql 那样的表格
from prettytable import PrettyTable
table = PrettyTable(["ID", "指令", "热键"])
table.align["指令"] = "l" # 左对齐
table.add_row([123, xxx, c-t])
print(table)
pyperclip
复制到系统剪切板/粘贴
import pyperclip
a = "hello"
pyperclip.copy(a) # Ctrl-V => hello
# Ctrl-C sth ↓
a = pyperclip.paste()
print(a)