C 与 C++相关内容,
一部分从网上摘录。
个人能力水平有限,
不能保证没有错误。
——王心意,2018.11.13
必知
1 | 【必知】整数/整数 = 整数,向下取整。 |
1 | a = 10, a += a *= 4; // 40 + 40 = 80 |
1 | a == b < c; // a == (b < c) // 判断 |
1 | for (; ; i++) { /*...*/} 中的 break 不会执行 i++ 语句,continue 会执行 i++ |
1 | 字符串复制:while (*p1++ = *p2++); // 谭浩强的书上有 |
1 | *p++; /* 地址++ */ |
读入回车解决方案
1 | getchar(); // 会读入上一个scanf的回车 |
计算机的浮点值计算有个”不确定尾数“的现象:
1 | 0.1+0.2 != 0.3 // 0.30000000000000004 |
原因在 https://0.30000000000000004.com
1 | char a[] = "hello"; strcpy(a, "hehe"); // 正确,字符串常量能当做临时的指针常量 |
1 | int a[5], (*p)[5]; // 指向数组的指针,下标必须明确 |
1 | puts(str[2])、strlen(&str[2])、gets(str+2); //都是从第二个位置开始。输入后末尾自动加上 '\0' (即 ASCII码的 0) |
1 | int a = 3; int &b = a; // 引用类型:对b操作就是对a操作,printf("%d", b)就是3 |
取 int
或者 long long
变量的某个数字,如:125 / 10 % 10; //倒数第二位
函数 floor( f )
对浮点数取整,但是因为浮点数误差,一般用:floor( f + 0.5);
浮点数陷阱:for (double i = 0; i != 10; i++); // 死循环
升级
1 | printf("%d %d %d", a, a++, a++); //在有些编译器上(实测如VC,不包括VS),从右到左执行,从左到右输出,a=10时会输出:12, 11, 10 |
1 | a = b = 3; // 是从右到左开始运算,而非从左到右 |
不使用临时变量交换两个整数
1 | a = a + b |
位运算:
1 | a = a ^ b; |
输出”\r”字符串,光标位置回调到行首,并逐字覆盖这一行的文字(可用于做表格)
1 | int i1, i2; // 分配空间(例子):i1:10008,i2:10004 逆序添加 |
static int m;
可以让其他文件不能调用变量或者函数,作用域只限于本模块
头文件不要定义全局变量(多次调用会报告已声明),可以用 extern
很大的数组比如几百亿的,开到全局(main函数外面,太大了也必须全局),会全部自动初始化成 0
(2^32) - 1
= 4294967295
,unsigned
的大小
-n 的内部表示是:(2 ^ 32) - n
;
判断素数:
1 | if (n < 2) return false; |
也可以素数筛按照倍数打表(多次用到或数字很大时)
int q = 0;
!q 的值在 0 和 非零 之间变换
1 | scanf("%*s"); // 可以跳过输入一个字符串,其他类型同理 |
1 | char t[] = "hello\0world"; |
数字转换成字符串:sprintf(str, "%d", num);
字符串转换成数字:sscanf(str, "%d", &num);
清空queue等STL的数据:que.swap( queue<T>() ) ;
//消亡值语义
void指针(仅用于代表地址):
1 | void zero(void * data, int bytesize) |
strncat
、strncpy
:比较安全的函数,第三个参数是数量,比如:sizeof(num)/sizeof(int)
指向函数的指针:
1 | typedef void (* intFunc)(int i); |
1 | ( a * 2 + 1 ) == ( a << 1 | 1 ); // 位运算速度快很多很多 |
1 | std::ios::sync_with_stdio(false); // 禁用 cin 和 stdin 的同步,大大加快C艹的cin速度,与scanf相差无几(约为三倍) |
1 | a / b % m == a % (m * b) / b // 取模 |
字符串常量[下标],例如:"qwerty"[4] == 'r'
C语言只有一维数组,但数组元素可以是另一个数组,仿真出多维数组
16进制用0x
开头,八进制用0
开头;小数常量默认double
,末尾可加f
表示为float
;整数常量默认int
,末尾可加l
表示是long
;其他格式同理(然而没必要的,有隐式转换)
随机数(例如rand()
函数)并不是真的随机(但有些新设备可以实现真随机)
叨唠
// C语言的爸爸是B语言没错
// 即使从面向过程的C语言转到面向对象的C++,你也不一定能找到对象。
// 计算机的减法乘法都是通过加法器来实现的(最终都可以分解为与或非逻辑门组合运算)
// 大部分程序员不用关注二进制是啥(但考试要考到)
// 你写程序的大部分时间不是在写代码
// 看到这句话将会很开心: 0 error,0 warning
// 项目中适当的注释往往比代码本身还重要
// 仅用顺序结构,分支结构和循环结果能写出所有的可计算函数。
// 可以用多个不同的编辑器看看这段注释的颜色:/*/zhushi/*/
偏僻知识点
来自网络
无符号int自动转换
1 | void foo() |
数组共同体
1 | union |
union这种类型,就是内部的变量共同使用一块空间,按照size大的分配,int i 占4个字节,char x[2] 占2个,所以一共分配了4个字节。
一共4个字节的内存,对应x来说相当于占用了低2个字节,而给x赋值的1,和10,就存在个位和十位上了(十六进制)
公用体公用一个内存区域sizeof(a)为共用体成员中长度最长的成员的size。即i
1 | int: (|_|_|_|_|_|_|_|_|)(|_|_|_|_|_|_|_|_|)(|_|_|_|_|_|_|_|_|)(|_|_|_|_|_|_|_|_|) |
分析:
1 | a.x[0] = 10 ============>(|0|0|0|0|1|0|1|0|) |
公用体公用sizeof(int)长度即4字节32为,则赋值后共用体内存为
1 | a: (|0|0|0|0|0|0|0|0|)(|0|0|0|0|0|0|0|0|)(|0|0|0|0|0|0|1|0|)(|0|0|0|0|1|0|1|0|) |
a.i 为4字节整型,则 i = 2^9 + 2^3 + 2^1 = 256 + 8 + 2 = 522
宏定义运算
1 |
|
宏定义运算尽量添加小括号
1 |
sizeof(union)
1 |
|
数组内存分配
1 | char str1[] = "abc"; |
解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;
而str5,str6,str7,str8是指针,它们指向相同的常量区域。
char <==> int
应该不算偏僻,ACM刷题经常会用到(刷题之外就用到的不多了)
1 | //一位整数 to char |
自增
1 | { |
总结:(a++),在整个表达式执行完后,才+1;
(++a),在整个表达式执行前就+1;
连接下一行
行尾添加反斜杠 \
如果在宏定义中使用,则真正代码也会包括换行。可用来做接口宏。
多行字符串也能用这个来拼接。
数组下标为0
数组下标为0时,指针p不分配空间
1 | Struct MutableLenArray |
任意字符串
使用 # 符号
1 |
|
str
的内容就是" test "
,也就是说#会把其后的符号 直接加上双引号。
符号连接
##
符号会连接两个符号,从而产生新的符号(词法层次),例如:
1 |
|
宏被展开后将成为:int INT_1;
变参宏
1 |
|
VA_ARGS是系统预定义宏,被自动替换为参数列表。
宏例外
当一个宏参数被放进宏体时,如果宏体(外部的宏)参数有#,则宏体的参数不会展开。若要展开,则需要借助第三个宏
1 |
函数参数指针与引用
函数参数指针和引用的区别:引用必须已经初始化,且不能为空;指针可以
const修饰指针
1 | int a = 1; |
const引用常量
const 引用能引用常量:const double &PI = 3.14
非const不能引用常量
const函数
int fun() const;
只读函数,不允许修改其中成员变量
const int *fun();
修饰的是返回值,返回常量(这里是指向常量的指针)
const对象只能调用const函数;const成员函数中无法调用非const成员变量,也无法修改const成员变量
如果在常函数中真的需要修改某个成员变量的数据,那么需要这个成员被 mutable修饰
C与C++的bool
- C++具有真的布尔类型,bool是C++中的关键字,在C语言中使用布尔类型需要导入头文件stdbool.h(在C11中bool应该是数据类型了)。
- 在C++中 true false 是关键字,而在C语言中不是。
- 在C++中 true false 是1字节,而C语言中是4字节。
C++的void*
- C语言中
void*
可以与任意类型指针 自动转换。 - C++中
void*
不能给其他类型的指针直接赋值,必须强制类型转换,但其他类型的指针可以自动给void*
赋值。 - C++为什么这样修改
void*
?
为了更安全,所以C++类型检查更严格。
操作符别名
某些特殊语言的键没有~,&符号,国际标准化组织为一些操作符规定了别名,以便使用这些语言的键盘也能输入正确的C/C++代码。 C95和C++98以后的语言标准都支持ISO-646
1 | and && |
确定函数的步骤
- 候选函数
函数调用的第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合中的函数就是候选函数。 - 选择可行函数
从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参。 - 寻找最佳匹配
优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。
new/delete和malloc/free的区别?
不同点:
方面 | new/delete | malloc/free |
---|---|---|
身份 | 运算符 | 标准库函数 |
参数 | 类型(自动计算) | 字节数(手动计算) |
返回值 | 带类型的地址 | void*地址 |
调用构造 | 自动调用 | 不能调用构造/析构函数 |
出错 | 抛出异常 | 返回NULL |
相同点:
都能管理堆内存
不能重复释放
可以释放NULL
当分配的内存过大,没有能满足需求的整块内存就会抛出异常
std::bad_alloc
new/delete/new[]/delete[]运算符重载
- C++缺省的堆内存管理器速度较慢,重载new/delete底层使用malloc/free可以提高运行速度
- new在失败会产生异常,而每次使用new时为了安全都应该进行异常捕获,而重载new操作符只需要在操作符函数中进行一次错误处理即可。
- 在一些占字节数比较小的类,频繁使用new,可能会产生大量的内存碎片,而重载new操作符后,可以适当的扩大每次申请的字节数,减少内存碎片产生的机率。
- 重载 new/delete 可以记录堆内存使用的信息
- 重载 delete 可以检查到释放内存失败时的信息,检查到内存泄漏。
类对象的创建过程与释放过程。
创建:分配内存(对象)-> 父类构造-> 成员构造-> 自己构造
父类构造:按照继承表从左到右依次构造。
成员构造:按照声明顺序从上至下依次构造。
释放:自己析构-> 成员析构-> 父类析构-> 释放内存(对象)
成员析构:按照声明顺序从下到上依次构造。
父类析构:按照继承表从右到左依次构造。
操作符重载
成员函数
1 | const 类 operator#(const 类& that) const |
全局函数(可以加友元)
1 | const 类 operator#(const 类& a,const 类& b) {} |
赋值类型时去掉左const
特殊操作符的重载
下标操作符
[]
1
类型& operator[](int i){}
函数操作符
()
一个类如果重载函数操作符,那么它的对象就可以像函数一样使用,参数的个数、返回值类型,可以不确定,它是唯一一个可以参数有缺省参数的操作符
解引用操作符
*
、成员访问操作符->
像指针一样使用(智能指针)
运算符重载的注意点
不能重载的操作符
- 域限定符
::
- 直接成员访问操作符
.
- 三目操作符
?:
- 字节长度操作符
sizeof
- 类型信息操作符
typeid
- 域限定符
重载操作符不能修改操作符的优先级
无法重载所有基本类型的操作符运算
不能修改操作符的参数个数
不能发明新的操作符
钻石继承
一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承。
钻石继承不会导致继承错误,但访问祖先类中的成员时每次需要使用 类名::成员名 ,重点是这种继承会造成冗余。
虚继承
当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。
注意:但使用虚继承时,子类中会多了一些内容(指向从祖先类继承来的成员)。
类型信息typeid
用于获取数据的类型信息,同时还支持 == != 用来比较是否是同一种类型,或者是否具有继承关系(但没法判断出实际new的对象类型)
1 | if (typeid(a) == typeid(b)) |
1 | Parent* p = new Child(); |
C语言主要关键词
基本类型:int
, short
,long
, signed
, unsigned
,char
,float
, double
,void
控制:if...else
, switch...case...default
, for
, while
, do...while
,return
, break
, continue
,goto
自定义类型:enum
, struct
, union
, typedef
修饰词:const
, static
, extern
, inline
, restrict
, volatile
运算符:+
, -
, *
,/
, %
,++
,--
,&
,|
,~
, ^
,&&
,||
,!
,<
,>
,<=
,>=
,==
,!=
, <<
,>>
, .
, ->
,?:
,sizeof
及复合运算符
预处理器:#include
, #define
, #undef
, #if/#ifdef/#ifndef...#elif...#else...#endif
, defined
, #pragma
,#error
有极少数关键字有多种语义,例如static
、void
掌握上面这些就算入门了。