*C++ set与map、unordered_map、unordered_set与哈希表
(1)排序:map在缺省下,map按照递增的顺序进行排序;unordered_map不排序
(2)内部原理:map内部采用了自平衡的二叉搜索树,实现了数据排序;unordered_map内部采用了哈希表
(3)搜索操作时间:map的搜索时间复杂度为O(log(n));unordered_map平均搜索时间O(1),最坏情况为O(n)
(4)插入操作时间:map复杂度为log(n)+再平衡时间;unordered_map平均插入时间O(1),最坏情况为O(n)
(5)删除操作时间:与插入操作时间复杂度一样
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
Reference
1、预处理: 展开头文件/宏替换/去掉注释/条件编译 ,生成.i文件。
2、编译: 检查语法,将预处理后的文件转换成汇编语言,生成.s文件
3、汇编:汇编变为目标代码(机器代码)生成.o的文件。
4、链接:连接目标代码,生成可执行程序。 链接过程是将单个编译后的文件链接成一个可执行程序。前面的预编译、汇编、编译都是正对单个文件,以一个文件为一个编译单元,而链接则是将所有关联到的编译后单元文件和应用的到库文件,进行一次链接处理,之前编译过的文件 如果有用到其他文件里面定义到的函数,全局变量,在这个过程中都会进行解析。
C的结构体和C++结构体的区别
C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。所以C的结构体是没有构造函数、析构函数、和this指针的。
C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。
以上都是表面的区别,实际区别就是面向过程和面向对象编程思路的区别:
C的结构体只是把数据变量给包裹起来了,并不涉及算法。
而C++是把数据变量及对这些数据变量的相关算法给封装起来,并且给对这些数据和类不同的访问权限。
C语言中是没有类的概念的,但是C语言可以通过结构体内创建函数指针实现面向对象思想。
二、C++的结构体和C++类的区别
2.1 C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。
2.2 C++结构体的继承默认是public,而c++类的继承默认是private。
直接用变量bai名访问是不行的,要通过函数调用来读写。例du如zhi:假设1.c文件里有静态变量static int a; 如果2.c文件想要读写a,就dao必须在1.c文件里添加读写a的外部函数。1.c写法示例:
static int a;
extern int get_a() { return a; }
extern void set_a(int v) { a=v; }
这样,在2.c里就可以调用get_a()和set_a(int v)函数来读写变量a了。
内存泄漏:
①访问已经释放的内存
②访问没有权限的内存
野指针:
指向内存被释放的内存或者没有访问权限的内存的指针。
https://blog.csdn.net/qq_35212671/article/details/51920851
atoi用法
atoi()函数的功能:将字符串转换成整型数;atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负号才开始做转换,而再遇到非数字或字符串时(’\0’)才结束转化,并将结果返回(返回转换后的整型数)。
#include
int my_atoi(char *input)
{
int flag = 1, result = 0;
if (input == NULL)
return 0;
while (*input == ' ' || *input == '\t')
++input;
if (*input == '- ')
flag = -1;
if (*input == '+ ' || *input == '- ' )
++input;
while (*input != '\0') {
if (*input >= 0 && *input <= '9')
result = result*10 + (*input - '0');
else
break;
++input;
}
return flag * result;
}
int main(void)
{
int output;
char input[32];
scanf("%s",input);
output = my_atoi(input);
printf("%d\n",output);
return 0;
}
#include
int my_atoi(char* pstr)
{
int Ret_Integer = 0;
int Integer_sign = 1;
/*
* 判断指针是否为空
*/
if(pstr == NULL){
printf("Pointer is NULL\n");
return 0;
}
/*
* 跳过前面的空格字符
*/
while(*pstr == '\0') {
pstr++;
}
/*
* 判断正负号
* 如果是正号,指针指向下一个字符
* 如果是负号,把负号标记Integer_sign置-1,然后再把指针指向下一个字符
*/
if(*pstr == '-') {
Integer_sign = -1;
}
if(*pstr == '-' || *pstr == '+') {
pstr++;
}
/*
* 把数字字符串逐个转换成整数,并把最后转换好的整数赋给Ret_Integer
*/
while(*pstr >= '0' && *pstr <= '9') {
Ret_Integer = Ret_Integer * 10 + *pstr - '0';
pstr++;
}
Ret_Integer = Integer_sign * Ret_Integer;
return Ret_Integer;
}
int main()
{
int output;
char input[32];
scanf("%s",input);
output = my_atoi(input);
printf("%d\n",output);
return 0;
}
有的操作符重载函数只能是成员函数
(1) 不能重载的运算符只有5个:
. ( 成员访问运算符 )
.* ( 成员指针访问运算符 )
:: ( 域运算符 )
sizeof ( 长度运算符 )
?: ( 条件运算符 )
(2) 运算符必须是成员函数
= ( 赋值 )
[] ( 下标 )
() ( 调用)
-> ( 成员访问箭头 )
(3) 只能为友元
>> ( 输入流操作 )
><< ( 输出流操作 )
面向对象与面向过程的区别
对象就是类,过程就是顺序,选择,循环,数组,函数
面向对象是以数据为中心,而面向过程以功能为中心
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些问题一步一步的实现,然后再使用的时候依次调用就可以了。
面向对象就是把构成问题的事物分解成各个对象,构建对象的目的不是来完成一个步骤的,而是为了描述某个事物在解决整个问题的步骤中的行为。
面向过程: 当需要实现一个功能的时候,每一个步骤我们都需要自己去做,处理实现功能的每一个细节。
面向对象:
当需要实现一个功能的时候,我们不需要自己去做,可以直接找一个已经具有该功能的东西,来帮我解决问题。
区别:
面向过程强调的是步骤。
面向对象强调的是对象,这里的对象就是饭店。
特点:
面向对象是一种更符合我们思考习惯的一种思想,他可以将问题简单化,并将我们从执行者变成了指挥者。在面向对象的语言中,有三大基本特性:
1.封装性
方法就是一种封装
private关键字也是一种封装
2.继承性
3.多态性
哈希冲突
哈希函数有五种实现方式:
A. 直接定址法:取关键字的线性函数值作为哈希地址。
B. 数字分析法:取关键字的中的若干位作为哈希地址。
C. 平方取中法:取关键字平方后的中间几位作为哈希地址。
D. 折叠法:将关键字分割成位数相同的几部分(最后一部分可以不同),然后取这几部分的叠加和作为哈希地址。
E. 除留余数法:H(key) = key MOD p ,p<=m ,m为不大于哈希表的数。
F. 随机函数法
上述五中实现方式中最常用的是除留余数法,而通过哈希函数寻址的过程可能出现“冲突”------即若干个不同的key却对应相同的哈希地址。解决哈希冲突有如下的方法:
作者:晚歌y
链接:https://www.jianshu.com/p/b9e6015703c2
来源:简书
哈希冲突的定义:某个哈希函数H对于不同的关键字K1,K2得到相同的哈希地址,即H(K1) = H(K2),这种现象称为~
Hash冲突发生的场景:当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。hash冲突就会发生。
Hash溢出发生的场景:当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新记录,不仅发生冲突,而且还会发生溢出。
解决哈希冲突的四种方法:
(1)线性探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。
(2)二次平方探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
(3)伪随机探测
按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
2.链地址法(HashMap的哈希冲突解决方法)
对于相同的值,使用链表进行连接。使用数组存储每一个链表。
优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
3.建立公共溢出区
哈希表分为基本表和溢出表。凡是和基本表发生冲突的元素,一律填入溢出表。
4.再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突。缺点:计算时间增加。
C++知识点14:静态链接库和动态链接库区别
库:函数的集合
作用:共享代码
静态、动态指链接
程序编译过程中,在链接阶段,程序生成的汇编文件和库进行链接,生成可执行文件。
Linux下得库有动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。面对比一下两者:
静态链接库:当要使用时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
动态库而言:某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))。
动态链接库的加载方式有两种:隐式加载和显示加载。
注意:linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接(见本文第四部分)。
“常量引用”其实是“对 const 的引用”的简称。
顾名思义,它把它所指向的对象看作是常量(不一定是常量),因此不可以通过该引用来修改它所指向的对象的值。
严格来说,并不存在常量引用,因为引用不是一个对象,所以我们没法让引用本身恒定不变。事实上,由于 C++ 语言并不允许随意改变引用所绑定的对象,所以从这层意思上理解所有的引用又都算是常量。
与普通引用不同的是,“常量引用”(即对 const 的引用)不能被用作修改它所绑定的对象。
(1)指向常量对象时,一定要使用“常量引用”,而不能是一般的引用。
因为不允许直接为常量赋值,当然也就不能通过引用去改变常量。因此直接规定当引用一个常量时,必须使用“常量引用”。
const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对应的对象都是常量
r1 = 42; // 错误:r1是对常量的引用,不能被用作修改它所绑定的对象
int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
(2)“常量引用”可以指向一个非常量对象,但不允许用该引用修改非常量对象的值。
必须认识到,“常量引用”仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:
int i = 42;
int &r1 = i; // 普通引用指向非常量对象 i
const int &r2 = i; // 常量引用也绑定非常量对象 i
r1 = 0; // 正确,r1并非常量引用
r2 = 0; // 错误:r2是一个常量引用
r2 绑定非常量整数 i 是合法的行为。然而不允许通过 r2 修改 i 的值。尽管如此,i 的值仍然允许通过其他途径修改,既可以直接给 i 赋值,也可以通过像 r1 一样绑定到 i 的其他引用来修改。
(3)引用的类型必须和所引用的类型严格匹配,且不能与字面值或者某个表达式的计算结果绑定在一起,但是 “常量引用” 是例外(只要被引用的类型能够转换为常量引用的类型)。
尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式:
int i = 42;
const int &r1 = i; // 正确:指向非常量对象
const int &r2 = 42; // 正确:r2 是一个常量引用
const int &r3 = r1 * 2; // 正确:r3 是一个常量引用
int &r4 = r1 * 2; // 错误:r4 是一个普通的非常量引用
下面的操作也是允许的:
double dval = 3.14;
const int &r1 = dval;
此处 const int &r1 = dval 编译器实际上相当于执行了下列语句:引用和原 dval 已经不是同一个地址了:
const int temp = dval; // 生成一个临时的整型常量
const int &r1 = temp; // 让 r1 绑定这个临时量
在这些情况下,“常量引用”实际上是绑定了一个临时量(temporary)对象。也就是说,允许“常量引用”指向一个临时量对象。
当 r1 不是“常量引用”时,如果执行了类似于上面的初始化操作会带来什么样的后果?
如果 r1 不是常量,就允许对 r1 赋值,这样就会改变 r1 所引用的对象的值。注意,此时绑定的对象是一个临时量而非 dval。程序员既然想让 r1 引用 dval,就肯定想通过 r1 改变 dval 的值,否则干什么要给 r1 赋值呢?如此看来,既然大家基本上不会想着把引用绑定到临时量上,C++ 语言也就把这种行为归为非法。
也就是说,不允许一个普通引用与字面值或者某个表达式的计算结果,或类型不匹配的对象绑定在一起,其实就是不允许一个普通引用指向一个临时变量,只允许将“常量引用”指向临时对象。
(4)在函数参数中,使用常量引用非常重要。因为函数有可能接受临时对象,而且同时需要禁止对所引用对象的一切修改。
下面程序执行发生错误,因为不可以将一个字面值常量赋值给普通引用;函数的返回值如果是非引用类型时,实际上是作为一个临时变量返回的,经过上面的讨论,不允许一个普通引用指向临时对象。
int test() {
return 1;
}
void fun(int &x) {
cout << x << endl;
}
int main()
{
int m = 1;
fun(m); // ok
fun(1); // error
fun(test()); // error
return 0;
}
按下面修改后,fun()函数无论是接受字面值常量作为参数,还是将函数的返回值作为参数均可:
int test() {
return 1;
}
void fun(const int &x) {
cout << x << endl;
}
int main()
{
fun(1); // ok
fun(test()); // ok
return 1;
}
复制构造函数也是构造函数,但它只有一个参数,这个参数是本类 的对象(不能是其他类的对象),而且采用对象的引用的形式(一般约定加const声明,使参数值不能改变,以免在调用此函数时因不慎而使对 象值被修改)。如果用户自己未定义复制构造函数,则编译系统会自动提供一个默认的 复制构造函数,其作用只是简单地复制类中每个数据成员。
拷贝(复制)构造函数定义及3种调用情况举例