C&CPP知识点

简介

因为最近做的笔试编程题,一般都用的c++/Python,一用到的时候便发现有些东西还是忘记了,所以重新翻书,温故。

C知识点

十进制化N进制

除N取余

比如13:
2|13 1
 2|6 0
  2|3 1
   1
将每次的余数倒着写上去为:1101,即为13

数据结构

取值范围

数据类型 所占位数 取值范围
char 8 -27~27-1
unsigned char 8 0~28-1
short 16 -215~215-1
unsigned short 16 0~216-1
int 32 -231-1~231-1
unsigned int 32 0~232-1
long 32 -231~231-1
unsigned long 32 0~232-1
long long 64 -263~263-1
unsigned long long 64 0~264-1
float 32 3.40282e+038
double 64 1.79769e+308
long double 96 1.79769e+308

输入输出格式

scanf

格式转换说明符
%d或%i 十进制整数
%o 八进制整数
%x 十六进制整数
%c 1个字符
%s 字符串
%f或%e float型,%f为小数形式,%e为指数形式
%lf double型
%% 1个百分号%
格式修饰符
l 加在d、i、o、x、u之前用于long型
加在f、e之前用于double型
L 加在f、e之前用于long double型
h 加在d、i、o、x之前用于short型
域宽m 指定输入数据的宽度(列数),系统自动按此宽度截取输入数据
* 忽略该输入

printf

格式转换说明符
%d或%i 十进制整数
%u 无符号的十进制整数
%o 八进制整数,不输出前导符0
%x 十六进制整数,不输出前导符0x
%c 1个字符
%s 字符串
%f float型和double型,默认6位小数
%e 指数形式
%g 自动选取f或e格式中输出宽度较小的一种使用,且不输出无意义的0
%% 1个百分号%
格式修饰符
l 加在d、i、o、x、u之前用于long型
L 加在f、e、g之前用于long double型
m.n 空格补齐,m包括小数点,n为小数后位数
- -表示左对齐,没有表示右对齐

PS:long long在windows下用%I64d(I是大写i),在linux下用%lld(l是L小写)。

运算符

优先级 运算符 含义 运算符类型 结合方向
1 () 圆括号 从左到右
[] 数组下标
-> 成员选择(指针)
. 成员选择(对象)
2 ! 逻辑非 单目运算符 从右到左
~ 按位取反
++ 自增
自减
- 负号
* 取值
& 取地址
sizeof() 长度
(类型) 强制类型转换
3 / 除法 双目算术运算符 从左到右
* 乘法
% 整数求余
4 + 加法 双目算术运算符 从左到右
- 减法
5 << 左移 双目位运算符 从左到右
>> 右移
6 < 小于 双目关系运算符 从左至右
<= 小于等于
> 大于
>= 大于等于
7 == 等于 双目关系运算符 从左至右
!= 不等于
8 & 按位与 双目位运算符 从左至右
9 ^ 按位异或 双目位运算符 从左至右
10 | 按位或 双目位运算符 从左至右
11 && 逻辑与 双目逻辑运算符 从左至右
12 || 逻辑或 双目逻辑运算符 从左至右
13 ?: 条件运算符 三目运算符 从右至左
14 = 赋值 双目运算符 从右至左
/=
*=
%=
+=
-=
<<=
>>=
&=
^=
|=
15 , 逗号 顺序求值运算符 从左至右

const

常指针

基类型名 *const 指针名=地址值;

int a=10,b=20;
int *const p=&a;

这样p不能被修改,只能修改*p

指向常量的指针

基类型名 const * 指针名;

const 基类型名 * 指针名;

int a=10;
int const * p=&a;

这样*p是不可以被修改的,而p可以被修改。

指向常量的常指针

const 基类型名 * const 指针名=地址值;

int a=10;
const int * const p=&a;

这样的p*p都不能被修改。

字符串

c语言的字符串输入,须定义为数组:

char a[10];
scanf("%s",a);

char a[]="apple";
scanf("%s",a);

char a[10],*b;
b=a;
scanf("%s",b);

下面这种操作是不允许的:

char *a;
scanf("%s",a);

一些字符串函数

头文件<string.h>
strlen: 长度,不包括'\0\
strcpy(靶,源): 复制
strcat(靶,源): 拼接
strcmp: 1: >,-1: <,0: =
strupr: 大写
strlwr: 小写

带参数的main

int main(int argc, char **argv)
argc为参数个数(包括命令本身),argv为各个参数,argv[0]为命令本身。

内存划分

一个由c/c++编译的程序占用的内存分为以下几个部分:

  1. 栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。
  2. 堆区(heap) — 在内存开辟另一块存储区域。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。
  3. 全局区(静态区)(static)—编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。而C++则没有这个区别 - 程序结束后由系统释放。
  4. 文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。
  5. 程序代码区—存放函数体的二进制代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a = 0; //全局初始化区  
char *p1; //全局未初始化区
int main()
{
int b; // 栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //"123456/0"在常量区,p3在栈上。
static int c =0;//全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域在堆区。
strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
return 0;
}

堆和栈

  • 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

  • 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的。

  • 碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题。

  • 生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

  • 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由malloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

  • 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址, EBP和局部变量都采用栈的方式存放。所以,推荐大家尽量用栈,而不是用堆。

new/delete与malloc/free比较

从C++角度上说,使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long类型。同样,delete在释放堆空间之前会调用析构函数,而free函数则不会。从结果可以看出,使用new/delete可以调用对象的构造函数与析构函数,并且示例中调用的是一个非默认构造函数。但在堆上分配对象数组时,只能调用默认构造函数,不能调用其他任何构造函数。

引用

当使用一般变量传递函数的参数时,当函数发生调用,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率更高,所占空间更少。

使用指针作为函数的参数虽然也能达到与使用引用同样的效果,但是在被调函数中同样 要给形参分配存储单元,且需要重复使用“* 指针变量名”的形式进行运算,者很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参,这些都不太方便。

综上所述,引用是个有效率的选择。

测试大小端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
union TEST{
short a;
char b[sizeof(short)];
};
int main(){
TEST test;
test.a = 0x0102; //不能引用共用体变量,只能引用共用体变量中的成员
if(test.b[0] == 0x01 && test.b[1] == 0x02){
cout<<"big endian."<<endl;
}
else if(test.b[0] == 0x02 && test.b[1] == 0x01){
cout<<"small endian."<<endl;
}
else{
cout<<"unknown"<<endl;
}
}

struct与union

union的内存单元占用字节数取最长的那个,另外需要考虑内存对齐。默认的内存对齐方式为double(8 Byte)对齐,所以比如最长的长度为20Byte,则需要占用到24Byte。

struct的内存单元占用字节数类似,从头开始,每个变量都要考虑内存对齐。总和之后再考虑内存对齐方式。

do…while(0)的妙用

加了do…while(0)后,将宏替换到,比如if下面,则还是一个整体。如果不加,那么替换到里面则会变成多个语句,发生错误。

struct与class

struct中的成员访问权限默认是public,而class中则默认是private的。在C语言里,struct中不能定义成员函数,而在c++中,增加了class类型后,扩展了struct的功能,struct中也能定义成员函数了。

3种基本权限

1
2
3
private:只限于类成员访问。
public:允许类成员和类外的任何访问。
protect:允许类成员和派生类成员访问,不运行类外的任何访问。

析构函数

程序执行析构函数的时机有以下4种:
(1)如果在函数中定义了一个对象,当这个函数调用结束时,对象会被释放,且在对象释放前会自动执行析构函数。
(2)static局部对象在函数调用结束时对象不释放,所以也不执行析构函数,只有在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
(3)全局对象则是在程序流程离开其作用域(如main函数结束或调用exit函数)时,才会执行该全局对象的析构函数。
(4)用new建立的对象,用delete释放该对象时,会调用该对象的析构函数。

析构函数的作用不是删除对象,而是在撤销对象占用的内存前完成一些清理工作,使得这些内存可以供新对象使用。析构函数的作用也不限于释放资源方面,它还可以被用来执行用户希望在最后一次使用对象之后所执行的任何操作。

静态数据成员

C++静态数据成员被类的所有对象所共享,包括该类的派生类的对象。派生类对象与基类对象共享基类的静态数据成员。静态的数据成员在内存中只占一份空间。如果改变它的值,则在各个对象中这个数据成员的值同时都改变了,这样可以节约空间,提高效率。另外可以成为类、对象之间通信的方式。

静态成员函数

静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。加对象名和成员运算符“.”可以引用本类的非静态成员。

对象的存储空间

最权威的结论是:非静态成员变量总和加上编译器为了CPU计算做出的数据对齐处理和支持虚函数所产生的负担的总和。
一个空类型的实例占1Byte空间,因为当声明该类型的对象的时候,它必须在内存中占有一定的空间,否则无法使用这些对象。
构造函数和析构函数也是不占空间的。

this指针

this指针特点:

  1. 只能在成员函数中使用,在全局函数、静态成员函数中都不能使用this。
  2. this指针是在成员函数开始前构造,并在成员函数的结束后清除。
  3. this指针会因编译器不同而有不同的存储位置,可能是栈、寄存器或全局变量。
  4. this是类的指针。
  5. 因为this指针只有在成员函数中才有定义,所以获得一个对象后,不能通过对象使用this指针,所以也就无法知道一个对象的this指针的位置。不过,可以在成员函数中指定this指针的位置。
  6. 普通的类函数(不论是非静态成员函数,还是静态成员函数)都不会创建一个函数表来保存函数指针,只有虚函数才会被放到函数表中。

构造函数与析构函数的执行顺序

一般类似栈,先进后出。
特殊情况:
(1)在全局范围中定义的对象,它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。
(2)如果定义的是局部自动对象(如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
(3)如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

派生类的构造函数与析构函数的调用顺序

(1)基类构造函数。。如果有多个基类,则构造函数的调用顺序时某类在类派生表中出现的顺序,而不是它们出现在成员初始化表中的顺序。
(2)成员类对象构造函数。如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
(3)派生类构造函数。

析构函数相反

虚函数

virtual void funtion();

基类中可以不用定义该函数,并且基类可以调用派生类的该函数。

纯虚函数

virtual void funtion()=0;

基类中没有定义,但要求任何派生类都要定义自己的实现方法。

string

string类,包含了字符串的一系列操作。

vector

动态的线性空间,类似Python的list。

map

映射,类似哈希,Python的dict。
内置为红黑树。

set

集合,无序容器

ELF

ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心存储的标准文件格式。ELF标准的目的是为软件开发人员提供一组二进制接口定义,这些接口可以延伸到多种操作环境中,从而减少重新编码、编译程序的需要。

ELF的文件类型

三种:

  1. 可重定位的目标文件
    这是由汇编器汇编生成的.o文件。可以使用ar工具将众多的.o文件归档成.a静态库文件。
  2. 可执行的目标文件
    可执行脚本不是可执行的目标文件。
  3. 可被共享的目标文件
    动态库文件.so文件。
一分一毛,也是心意。