C++知识总结

class和struct区别

C里面没有class。

C和C++的结构体

  1. C里面结构体不能有成员函数,没有构造函数、析构函数、this指针;
    C++结构体可以有成员函数,还可以是虚函数;
  2. C里面结构体没有访问权限概念,统一public;
    C++结构体可以是public、private、protected;
  3. C里面结构体不能继承;
    C++结构体可以是从别的结构体、类继承的。

C里面的空结构体内存大小是0,C++的空结构体/类内存大小是1,因为空结构体/类也可以实例化对象,实例应该有自己的this指针,独一无二的内存地址,所以空类加一个隐含的字节。

C++结构体和类

除了默认访问权限之外没啥区别。

C#类和结构体

相同:C#结构体和类同样能够定义字段,方法和构造函数,都能实例化对象;
不同:

  1. C#的类是引用类型的,结构体是值类型的,类传递的时候改变值,同时修改源对象;
  2. C#的结构体不可以定义无参构造函数,类可以定义无参构造函数。

C++如何调用C代码

OpenSceneGraph是用标准C++写的,基于OpenGl的高性能图形应用程序开发框架。
LeapC是用C语言写的API,是基于手势追踪的产品leap motion、ultraleap的.
笔者在写osg程序的时候用到了leapC的代码,把leapC写的代码封装成头文件,在前面加上:

#ifdef _cplusplus
extern "C"{
#endif

在末尾加上:

#ifdef _cplusplus
}
#endif

这样把c语言的代码良好封装,无论是用C++代码调用到此头文件,还是用C代码调用到此头文件,都不会报错。

分文件编写里的static和extern

static表示静态存储,直到整个程序结束变量值才会从存储区释放,静态变量又分为全局静态变量和局部静态变量。

全局变量与全局静态变量的区别:

(1)若程序由一个源文件构成时,全局变量与全局静态变量没有区别。
(2)若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该程序的其它源文件是无效的。而全局变量,除了本文件,其他文件可以通过extern的方式调用。

静态全局变量的作用:

(1)不必担心其它源文件使用相同变量名,彼此相互独立。
(2)在某源文件中定义的静态全局变量不能被其他源文件使用或修改。
(3) 只能在本文件中使用,不允许在其他文件里调用。

静态局部变量

静态局部变量的作用域和局部变量一样,不过生存周期不一样,局部变量在定义局部变量的函数调用完之后就从内存中释放其值,而静态局部变量不释放,等整个程序全部执行结束后才会从内存中释放。

extern关键字

如果要在多个文件里共享一个变量,除了全局变量外,还可以加extern关键字。加了extern关键字的变量声明语句,表示本变量在其他文件有定义。
而全局变量不加extern关键字,想要分文件调用,则需要头文件的包含。比如A包含B,A能用B里面的全局变量,而B不能用A里面的全局变量。

static不能和extern一起用

此外,static不能和extern一起,是冲突的。extern修饰全局变量和函数,被修饰的变量和函数可以在别的文件里使用。而static修饰的变量和函数作用范围仅限于定义它的文件内部。

C++多个文件共用一个全局变量

头文件:state.h 源文件:state.cpp
其它源文件:t1.cpp t2.cpp t3.cpp, 这些源文件都包含头文件state.h。
需要定义一个全局变量供这些源文件中使用:方法如下
1、在 state.h声明全局变量: extern int a;
2、在state.cpp中定义该全局变量:int a =10;
这样其它源文件就可以使用该变量啦

C++里INT_MAX和INT_MIN的定义

#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)

在C/C++语言中,不能够直接使用-2147483648来代替最小负数,因为这不是一个数字,而是一个表达式。表达式的意思是对整数21473648取负,但是2147483648已经溢出了int的上限,所以定义为(-INT_MAX -1)。

C++ function

添加链接描述

牛客C/C++工程师测试

1、new分配内存和初始化

int *p1 = new int[10]; int *p2 = new int[10]();

//p1申请的空间里的值是随机值,p2申请的空间里的值已经初始化

对于内置类型,new仅仅是分配内存,加()相对于调用了它的构造函数;
对于自定义类型,调用new则不仅仅是分配内存,还调用默认构造函数进行初始化。

2、访问函数外部定义的变量是0,访问函数内定义的变量是随机值,因为没有初始化。

对于enum string{ x1, x2, x3=10, x4, x5, } x;
函数外部访问x等于什么?
答案:0
访问函数外部定义的变量是0,访问函数内定义的变量是随机值,因为没有初始化。

3、数组作为函数的参数退化为指针
void example(char acWelcome[]){
    printf("%d",sizeof(acWelcome));
    return;
}
void main(){
    char acWelcome[]="Welcome to Huawei Test";
    example(acWelcome);
    return;
}
输出:4
32位系统是4,64位系统是8;32位一个字节占4位,64位一个字节占8位。
4、
unsigned char *p1;
unsigned long *p2;
p1=(unsigned char *)0x801000;
p2=(unsigned long *)0x810000;

请问p1+5= 什么?
p2+5= 什么?

指针跳类型不跳字节,p1是char类型,一次移动1个字节;p2是long类型,一次移动4个字节。
p1跳5个字节,加上段地址0X801000,得0X801005;
p2跳4*5得20个字节,又因为是十六进制表示,所以是14,加上段地址0X810000,得0X801014。
所以,答案是

801005   810014

注释:char每次移动1个字节;short移动2个字节 ;int , long ,float移动4个字节 ;double移动8个字节

5、
void Func(char str_arg[100])
{
       printf("%d\n",sizeof(str_arg));
}
int main(void)
{
     char str[]="Hello";
     printf("%d\n",sizeof(str));
    printf("%d\n",strlen(str));
    char*p=str;
    printf("%d\n",sizeof(p));
    Func(str);
}

32位系统下下面程序的输出结果为多少?
6 5 4 4

void Func(char str_arg[100])
{
       printf("%d\n",sizeof(str_arg));
       //数组名作为函数参数传递时,退化为指针
       //所以str_arg其实是指向数组头部的指针
}
int main(void)
{
     char str[]="Hello";
     printf("%d\n",sizeof(str));  
     //str是char的数组,末尾包含'/0' 5+1 得6
    printf("%d\n",strlen(str));
    //strlen求字符串长度 不包括'/0'
    char*p=str;
    printf("%d\n",sizeof(p));
	//p是指针,占4个字节
    Func(str);
}
6、
下面程序运行后的结果为?
	
	char str[] = "glad to test something";
	char *p = str;
	p++;
	int *p1 = reinterpret_cast<int *>(p);
	p1++;
	p = reinterpret_cast<char *>(p1); 
	printf("result is %s\n", p);

result is glad to test something
result is ad to test something
result is test something
result is to test something      √

reinterpret_cast,强制类型转换
p为char*,指针++是跳类型不跳字节,所以p++是往后移动1个字节;p1经过reinterpret_cast之后,类型是int*,p++是往后移动4个字节。
所以"glad to test something",从头+5之后,得到"to test something"。

7、全局变量、静态局部变量和局部变量空间的堆分配和栈分配
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?
	
	C c;
	void main()
	{
	    A*pa=new A();
	    B b;
	    static D d;
	    delete pa;
	}

答案:A B D C

这道题主要考察的知识点是 :全局变量,静态局部变量,局部变量空间的堆分配和栈分配

其中全局变量和静态局部变量是从静态存储区(栈区)中划分的空间,二者的区别在于作用域的不同,全局变量作用域大于静态局部变量(只用于声明它的函数中)。
而之所以是先释放 D 在释放 C的原因是, 因为C是全局变量,所以程序中首先调用的是 C的构造函数,然后调用的是 D 的构造函数,析构函数的调用与构造函数的调用顺序刚好相反。

局部变量A 是通过 new 从系统的堆空间中分配的,程序运行结束之后,系统是不会自动回收分配给它的空间的,需要程序员手动调用 delete 来释放。
局部变量 B 对象的空间来自于系统的栈空间,在该方法执行结束就会由系统自动通过调用析构方法将其空间释放。
之所以是 先 A 后 B 是因为,B 是在函数执行到 结尾 “}” 的时候才调用析构函数, 而语句 delete a ; 位于函数结尾 “}” 之前。

C++ STL prev()函数

1.直观理解

new_iterator = prev(iterator,n)

当“n“为正数时,返回传入迭代器“iterator”左边,距离”iterator“ n个单位的迭代器”new_iterator“。

当“n“为负数时,返回传入迭代器“iterator”右边,距离”iterator“ n个单位的迭代器"new_iterator"。

new_iterator = prev(iterator)

不写n的话,默认向“iterator”左边移动1个单位。

如果是随机访问迭代器,就只执行一次运算符操作 +=n( -=n ),否则,执行n次持续的递减或递增操作 ++(–)。

2.prev()的函数原型
template< class BidirIt >BidirIt prev(
	BidirIt it, 
 	typename std::iterator_traits<BidirIt>::difference_type n = 1 );
 
template< class BidirIt >constexpr BidirIt prev(
  	BidirIt it, 
  	typename std::iterator_traits<BidirIt>::difference_type n = 1 );

prev()的含义:
表示迭代器左移n个单位,即迭代器-n。
若移动n个单位的步长后,超出迭代器范围[begin,end),则此行为未定义。

prev()函数返回值:
函数的返回值为一个迭代器,也就是传入迭代器左移n个单位后,返回这个移动后的新迭代器。

3.测试代码:
 vector<int> vec{ 1,2,3,4,5,6,7 };
 vector<int>::iterator end = vec.end();
 for (int i = 1; i <= vec.size(); ++i)
 {
  auto it = prev(end, i);
  cout << "end左移" << i << "个单位后的元素值为:" << *it << endl;
 } 


end左移1个单位后的元素值为:7
end左移2个单位后的元素值为:6
end左移3个单位后的元素值为:5
end左移4个单位后的元素值为:4
end左移5个单位后的元素值为:3
end左移6个单位后的元素值为:2
end左移7个单位后的元素值为:1

C++11对map容器的访问:下标访问、at()、find、lower_bound&upper_bound、equal_range1

C++中对map容器的访问有好几种方式,最简单的就是直接通过下标访问,但是这种模式有一个缺点就是如果被访问元素不在map中,会插入此元素并初始化,下面详细介绍几种访问方式:

1 下标访问:

map<int ,int> m;
m[1]=111;
m[2]=222;
m[3]=333;
cout<<m[4];

此时输出:

0

可见下标访问输出了一个本来没有的m[4]。

2 at(i)访问:

map<int ,int> m;
m[1]=111;
m[2]=222;
m[3]=333;
cout<<m.at(4);

此时会抛出一个异常表示没有找到key=4这个元素。所以这是一种相对安全的访问方式。

3 find访问

multimap<int ,int> m;
m.insert({1,11});
m.insert({1,12});
m.insert({1,13});
m.insert({1,14});
m.insert({2,21});
m.insert({3,31});
auto Find=m.find(1);
auto Count=m.count(1);
while(Count)
{
    cout<<Find->second<<endl;
    Find++;
    Count--;
}

输出:

11
12
13
14

此时可以输出所有key=1的元素,细心的同学已经注意到此时的容器已经变为了mapmulti并且元素的添加方式也已经改变了,multimap容器的元素添加方式不同于map,不能直接用m[key]=value的方式直接添加元素,必须用insert或者emplace。

4 一种面向迭代器的解决方法lower_bound\upper_bound

multimap<int, int> m;
m.insert({ 1,11 });
m.insert({ 1,12 });
m.insert({ 1,13 });
m.insert({ 1,14 });
m.insert({ 2,21 });
m.insert({ 2,22 });
m.insert({ 3,31 });
//lower_bound(val) 返回的是键比val小于等于的值最大的元素的迭代器 (包括val本身
//upper_bound(val) 返回的是键比val大的值最小的元素的迭代器 (不包括val本身
for (auto lo = m.lower_bound(1), hi = m.upper_bound(2); lo != hi; lo++)
{
	cout << lo->second << endl;
}

输出

11
12
13
14
21

可以见到,此时不仅可以输出key为1的元素,也能输出key为2的元素。

5 equal_range 不太懂

multimap<int ,int> m;
m.insert({1,11});
m.insert({1,12});
m.insert({1,13});
m.insert({1,14});
m.insert({2,21});
m.insert({3,31});
for(auto pos=m.equal_range(1);pos.first!=pos.second;pos.first++)
{
    cout<<pos.first->second<<endl;
}

输出

11
12
13
14

equal_range会返回一个pair,first是一个迭代器,指向匹配的第一个元素,second指向后一个。

C语言中关于int、long int和long long

简单来说: long long 和 int 的区别

区别如下:

占内存长度不同和取值范围不同。

32位系统:long是4字节32位,int是4字节32位。

64位系统:long是8字节64位,int是4字节32位。

注意事项:

1、long类型的范围是:-9223372036854775808~9223372036854775807。
2、如果只用正数可以考虑用unsigned long long范围是:0~18446744073709551615。

对象类型:

long、int占多少字节,得看计算机cpu是多少位的。16位机器上,int2字节,long4字节,32位机器上二者都是4字节,64位机器上,int4字节,long8字节。

1、关于int 和 long int

(1)在VC下没有区别。两种类型均用4个字节存放数据。
(2)VC是后出的编译器,之前有很多早期的C编译器,在早期编译器下long int占4个字节,int占2个字节。
(3)之所以有“整型”和“长整形”两种不同类型,是C语言在诞生时发明者规定好的,前者存储的整数的值域小于后者。

在VC下int和long int用谁都可以。

(4)在标准中,并没有规定long一定要比int长,也没有规定short要比int短。
标准是这么说的:长整型至少和整型一样长,整型至少和短整型一样长。
这个的规则同样适用于浮点型long double至少和double一样长,double至少和float一样长。
至于如何实现要看编译器厂商
(5)short int<=int<=long int

2、关于long long

long long是long long int的缩写,VC里面long long 是占8个字节(2^64)的,仅在64位平台上有效。

long 和 long long的区别

long关键字表示一种长整型数据,是编程语言中的一种基本数据类型,为long int 的缩写,默认为有符号长整型,含4个字节,取值范围为:-2^31 ~ (2^31 -1)。

LongLong(LongLong integer) 变量存储为已签名的64位 (8 字节) 数字, 值范围为-9223372036854775808 到9223372036854775807。

LongLong 仅在 64 位平台上是有效声明类型。

关于long long

(1)long long在win32中是确实存在,长度为8个字节;定义为LONG64。
也就是说,每种类型长度,需要sizeof才知道,如果可能,最好用union看看里面的数据,可以消除一些类型的假象长度。
(2)visual c++ 6.0中不支持,long long int,是在vc99中添加此功能的,所以我们在vc6.0中编译有long long 的数据时,会出错,但是在VS更高的版本中,是能通过的

用int和short int都行,数值区间大于2^31-1时用long long。(VC里是这样的)

const成员变量和const成员函数

1. const修饰成员变量

const修饰成员变量和const修饰普通变量相似,只需要在声明的时候加上const关键字。初始化const成员变量只有一种方法,就是通过构造函数的初始化列表。

2. const修饰成员函数

const成员函数可以使用类中的所有成员变量,但是不能修改。这种措施可以用来保护成员变量的值,const修饰的成员函数也被称为常成员函数,声明和定义处都需要添加const关键字。
返回值为常量的函数:const char* getName () {}
常成员函数:char* getName () const {}

可调用对象

对于一个对象或者表达式,如果可以对其使用调用运算符,则说其是可调用的,称其为可调用对象。
可调用对象包括:函数、函数指针、重载了函数调用运算符的类(函数对象)、lambda表达式
注意: 谓词不属于可调用对象,因为其使用时无需调用运算符,即“()”
eg:

// 谓词的使用
bool weici(const char& ch1, const char& ch2){
	return ch1 > ch2;
}
...
string str="eryuanweici";
sort(str.begin(),str.end(),weici);

关于C++ 封装、继承、多态三种特性

封装实现了代码的模块化,继承可以扩展已经存在的代码,都是为了代码重用。
封装指的是对抽象出的数据和方法,结合形成一个整体,只使用其外部接口就可以完成功能。
继承是指类可以继承另一个类,称为子类和父类,子类可以使用父类的数据和方法,也可以进行重写,覆盖父类原有的属性和方法,获得与父类不同的功能。
多态是在程序运行时根据传入的参数动态地决定函数的功能,其又可以分为静态多态和动态多态。
其中静态动态又称为静态绑定,在编译期间完成,一般是通过函数重载和泛型编程实现,函数重载指的是根据函数的形参列表不同,完成不同的功能。重载函数的调用是在编译时候就确定的,又称为早绑定。
泛型编程指的是编程独立于具体的类型,主要实现为模板函数和模板类。函数模板并不是真正的函数,它是C++编译生成具体函数的一个模子,实际生成的是替换函数模板的那个函数,函数模板不允许自动类型转换,不可以设置默认实参。
动态多态是通过类的继承和虚函数完成,在继承体系中,让父类的引用或指针指向子类的对象,在运行的时候根据子类的状态确定父类的工作方式。对于相同的功能类型,确定其共同的功能集合,在基类中将其声明为虚函数接口,然后在子类中对虚函数进行重写,让父类的引用或指针指向子类的对象,对虚函数的调用会自动绑定到对应的子类对象上去。

类的访问权限 和 继承方式

类的访问权限

类的访问权限分为public、protect、private。
public:允许类内部访问,类外部访问。
protect:允许类内部访问,不允许类外部访问。
private:允许类内部访问,不允许类外部访问。

继承方式

类的继承方式分为public、protect、private。
public:父类对象中成员的访问权限在子类中不变,子类内部可以访问public成员、可以访问protect成员,无法访问private成员。类外部,子类对象只可以访问public。
protect:父类对象中成员访问权限public——protect,子类对象内部可以访问public和protect,无法访问private。类外部,子类对象无法访问任何成员。
private:父类对象中访问权限public——private,protect——private,子类对象内部可以访问public和protect。类外部,子类对象无法访问任何成员。

为什么多态时,父类的析构函数要写成虚函数?

由于多态的父类子类继承关系,父类对象释放的时候,父类的析构函数不会调用子类的析构函数,导致子类对象释放不完全,而写成虚函数可以让父类的析构函数调用子类的析构函数,导致子类对象完全释放。

多态中,动态绑定是如何实现的

当编译器发现类中有虚函数的时候,会在内存中生成一个虚函数表,将虚函数的地址存放在虚函数表中,并在对象中创建一个指向虚函数的指针vptr,当子类对父类虚函数进行重写覆盖时,将虚函数表的地址进行更新,从而调用子类更新后的函数,实现动态绑定。

虚函数表是针对类的还是针对对象的

虚函数表是针对类的,不同对象共享一个虚函数表,每个对象内部有一个指向虚函数表的指针vptr,vptr的地址是不同的,但是指向的是内存中的同一个虚函数表。

为什么构造函数不能写成虚函数?

虚函数的调用依赖于虚函数表,而指向虚函数表的指针是在构造函数中初始化的,如果构造函数写成虚函数,就无法在内存中找到虚函数表,从而完成不了多态。

纯虚函数?

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,所以把父类函数写成纯虚函数,使得子类必须对该父类函数进行重写。如果一个类中有了纯虚函数,则该类成为抽象类,抽象类无法实例化对象,而且如果子类不对父类对象的函数进行重写,则子类也成为抽象类。

定义纯虚函数的作用

定义纯虚函数是实现一个接口,起到规范的作用,想要继承这个类就必须重写该函数。

string与其它类型进行转换的几种方法

to_string

to_string是C++里面提供的方法

string to_string(Elemtype e) 

int——string

1. stoi itos

还有类似的 atoi itoa

2. stringstream
int a;
char* ch="haoge";
stringstream ss(ch);
ss>>a;

char——string

1. C++提供的c_str
char* ch;
string s="abcde";
// ch=s.c_str()的安全写法
strcpy(ch,s.c_str());
2. stringstream

位运算

eg 不使用±运算符的两个数之和 (leetcode 371)

class Solution {
public:
    int getSum(int a, int b) {
        if(a == 0 || b == 0) return a | b;
        int sum = a^b; 
        unsigned int carry = a & b; 
        return getSum(sum, carry << 1); 
    }
};

不能用+、-,所以用位运算符。

位异或运算符 ^ :相异为1
位与运算符 & :全11
位或运算符 | :全00

位移运算符
>> : 二进制右移一位,头位补符号位,即正数补0、负数补1
<< : 二进制左移一位,低位补0

1、异或运算符相当于是不进位的加法
2、与运算符可以找到需要进位的位置,全1为1,然后左移一位
上面两步的结果加起来即是问题的答案(如果是求两个正整数加的话)
但问题需要考虑到负数的情况,所以用unsigned int和循环+判断是否为0。
十进制转二进制

#include
using namespace std;
void main()
{
   int n,i,j=0;
   int a[1000];
   cin>>n;
   i=n;
   while(i)
   {
    a[j]=i%2;
    i/=2;
    j++;
    
   }
   for(i=j-1;i>=0;i--)
    cout<<a[i];
   cout<<endl;
}

空悬指针、野指针;内存泄漏、内存溢出

在C语言中,指针的功能十分强大,这使得在C中程序员对于指针的使用要十分地谨慎。那么首先我们对于指针的使用就是要对空悬指针(dangling pointer)和野指针进行避免。

首先我们介绍空悬指针,空悬指针指的是一个指针,当它指向的对象已经被释放的时候而自身却没有被置为null的时候,那么这个指针就会变成一个空悬指针。而野指针,通俗的表示就是没有进行初始化的指针,但是因为一个指针没有初始化的时候会一通乱指,这个时候就类似于空悬指针了,所以有的介绍里面将空悬指针作为野指针的一种来进行描述。实际上我们只需要明确一点:因为我们在动态申请内存并用指针进行指向该块内存,在该内存被free/delete的时候仅仅是释放了这块申请的内存(注意一点释放之后就是告诉系统这块内存可以被其他地方申请到了),但是指针仍然指向这一块内存,那么如果我们后续使用到这个指针,但是这个指针仍然指向的是原来的内存地址,因为在释放之后这块内存地址就可以被其他的地方申请到了,那么在有的时候就会发生灾难性的后果,那么这个时候我们需要的就是无论在初始化的时候还是在最后free/delete的时候都要将指针置为null,对于内置类型这些由编译器进行回收内存的类型在生存期结束之后也要置为null(因为如果指向它的指针的生存期比该数据长的话依旧有概率出错)。虽然说我们在初始化指针的时候也要将指针置为null,但那是在我们声明一个指针却没有为其定义一个指向的对象的时候的做法。实际上当我们为其指明了一个指向的对象的时候就不用了。

关于指针使用的一些注意事项,这里主要介绍一些关于内存泄漏(memory leak)和内存溢出(out of memory)的:

举个很简单的例子,内存泄漏指的是程序中有申请内存的过程,但在程序结束之后,始终没有或者无法释放占有的内存的则称为内存泄漏,单次的内存的泄漏可能用户并不会察觉,但是累积的内存泄漏将会十分可怕,无论内存的大小,终会被耗光。也是因为我们在写代码的时候内存泄漏的问题并不严重,因此基本上没有表现出问题,而且现在的操作系统是不可能将全部的计算机内存都分配给IDE的,只会预先划分一块内存给IDE,这时候即使出现内存泄漏情况,也不会到影响整台电脑的情况,所以一般小规模的内存泄漏也不会出现什么大问题,但是作为一名程序员,在语言本身不提供内存回收机制的情况下知道及时释放和回收内存是最基本的知识,所以必须要进行内存的释放。

知道了内存泄漏,知道内存溢出就变得简单了许多,内存溢出就是可用的内存均被占用,无法申请内存的情况。

什么是STL

STL是标准模板库,是C++支持的基于泛型编程的库,建立数据结构和算法的一套标准。
STL使得开发者可以在不了解底层数据结构和内部如何运行的情况下,通过泛型编程提供的统一的外部接口进行编程任务,建立数据结构和算法的一套标准,提高了代码的复用性。
其包含容器、算法、迭代器、仿函数、容器适配器、空间配置器

reference to non-static member function must be called

LeetCode题目
在自定义容器排序规则时,比如用二元谓词定义排序规则,需注意
如果二元谓词定义在类内部,则可能报此错误。

class Solution {
public:
    bool mySort(const int& a, const int& b){
        if(to_string(a)+to_string(b)>=to_string(b)+to_string(a))
            return false; // false 把b放前面
        else 
            return true; // true 把a放前面
    }
    string minNumber(vector<int>& nums) {
        // 直接对nums进行排序
        sort(nums.begin(),nums.end(),mySort);
    }
};

sort函数需要调用的函数指针(即二元谓词)需要为普通函数指针,而类内部成员函数的函数指针的函数参数列表里包括指向本类的一个this指针,所以需要将此二元谓词声明为static静态成员函数,静态成员函数是没有this指针的,因为其不从属于类的具体的实例对象;或者将此二元谓词写在全局亦可。

补充:

在二元谓词里 对于输入 a、b
返回true即表示顺序不变 a在b前
返回false则表示顺序改变 b在a前

STL的自定义排序规则,不能在STL数据类型定义时 / 算法(algorithm)语句中,用lambda、谓词,需要用仿函数(即函数对象。

eg:

// WRONG EG
class Solution{
public:
	static bool mySort(const int& a, const int&b){
		return a>b;
	}
	void func(vector<int>& arr, int k) {
		vector<int> ret(k);
		// 优先级队列默认是大顶堆,此处欲改写成小顶堆
		// 大顶堆优先队列,把大的往队头排,小的往队尾放,所以返回小的
		// 小顶堆优先队列,把小的往队头排,大的往队尾放,所以返回大的
		priority_queue<int, vector<int>, mySort> pq;
	}
};

关于LeetCode ACM执行出错的一点想法

ACM执行出错包括除语法错误而产生的编译错误之外的,除去代码逻辑错误之外的所有错误。
比如

  1. 下标溢出,
  2. 内存创建失败(如创建较大的vector,或者堆栈溢出,递归太多次),
  3. 数值溢出(没有用long long而使用int),
  4. 在int main()函数里该写return 1表示正常退出,写成了return 0等等。
基于范围的for循环是只读的

进行基于范围的for循环时,仅支持对元素的访问,而不支持修改,所以最方便的写法就是

  1. 不用基于范围的for循环,而是用常规for循环,这样的好处是还可以获得下标,省得发现循环内主体需要下标又重新来写…
  2. 用引用,如for(int& i:vec){}
    eg:
std::vector<int> vec {1,2,3,4,5};  

for (auto n :vec)  
    n+=10;           // 无效
for (auto n :vec)  
    std::cout << n;  // 12345
    
for (auto& n :vec)  
    n+=10;           // 无效
for (auto n :vec)  
    std::cout << n;  // 1112131415

菱形继承

eg:多继承

class Animal{};
class Sheep:public Animal{};
class Tuo:public Animal{};
class SheepTuo:public Sheep,public Tuo{};

在多继承里,如果子类继承的多个基类又继承了同一个基类,则子类继承了多份相同的数据,在使用中会出现命名冲突、资源浪费等问题。
通过虚继承来解决,在继承中,子类声明为:

class Animal{};
class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
class SheepTuo:public Sheep,public Tuo{};

虚继承的目的是让派生类做出声明,承诺共享它的基类,通过虚继承被继承的基类被称为虚基类,使得在继承体系中,无论虚基类出现多少次,在派生类中都只包含一份虚基类的成员。
虚继承只影响从虚基类的派生类派生出的类。如示例中,派生类Sheep声明的虚继承,影响的是SheepTuo这个派生类的派生类。

c++代码编译成可执行文件的过程

  1. 预处理:宏定义处理(宏定义的替换)、条件编译语句(ifdef ifndef)、头文件包含指令
  2. 编译:通过语法分析,确认所有的指令符合语法规则后,将源代码翻译成中间代码或者汇编代码(生成.obj
  3. 汇编:将汇编语言代码翻译成目标机器指令
  4. 链接:静态链接和动态链接

编译阶段将源程序.c .cpp .h转换成.obj目标代码,在链接阶段,将程序里包含的库与obj链接起来形成可执行文件。

静态链接和动态链接的区别

静态链接 是在包含静态库的程序生成的可执行文件里复制一份代码文件,缺点是冗余拷贝。在一个包含静态库的工程里,编译成功后,只产生一个lib文件。
动态链接 是可以由多个程序同时使用的库,DLL不是可执行的文件,动态库提供一种方法,使进程可以调用不属于其可执行代码的函数。包含动态库的工程,生成成功后,产生一个.lib和一个.dll文件。lib是编译时候用到的,lib标示DLL文件,程序运行时需要的动态库的函数由进程空间中DLL提供。
总之,lib是编译时候用到的,要使包含动态库的程序运行起来,需要DLL。

C++中函数调用过程并解释栈帧的概念

c++函数调用过程:c++中的函数调用是用栈来实现的。调用者先调用的函数先入栈,后调用的函数**栈。等被调用函数执行完后,按照相反的顺序在出栈。过程是:函数参数代入、函数栈帧开辟、函数返回值、栈帧回退

栈帧:栈帧是一种用于实现函数调用的数据结构,是函数调用涉及信息的记录单元

你可能感兴趣的:(c++,leetcode,c语言)