C/C++语法知识相关
指针和引用区别
- 引用不可以为空,指针可以为空
- 引用必须初始化,而且一旦初始化后不能更改(只能是初始化时变量的引用),指针可以更改指向
- 对指针和引用进行运算操作时结果不同。比如对引用进行++,改变引用的对象,对指针进行++,指针移动位置,指向别的变量
- 引用大小为所指向变量的大小,指针大小为指针本身大小。
- 引用比指针更安全。指针存在野指针问题。
- 可以有const指针,没有const引用(因为引用本身就是常量)
- 指针可以有多级,引用只可以有一级
- 传递参数时,指针传递的是参数的拷贝,引用传递的是本身
虚函数和纯虚函数
- 定义一个函数为虚函数,不代表函数为不被实现的函数,是为了允许基类指针调用子类函数
- 定义一个函数为纯虚函数,是为了实现一个接口,基本不进行实现,子类必须实现
- 析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数
参考:https://www.cnblogs.com/ganxiang/p/13054500.html
public,protected,private
用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。
protected成员可以被派生类对象访问,不能被用户代码(类外)访问。
public继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private
protected继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected, protected, private
private继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private
静态多态和动态多态
静态多态:也称为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
静态多态有两种实现方式:
- 函数重载:包括普通函数的重载和成员函数的重载
- 函数模板的使用
动态多态(动态绑定):即运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
虚继承
解决多继承中的冲突问题
类B和类C继承自类A,类D继承了类B和类C,这时类D拥有了两份类A中的成员,如果类B和类C为虚继承自类A,那么类D仅拥有一份类A的成员。
C++空类默认产生哪些成员函数?
默认构造函数、默认拷贝构造函数、默认析构函数、默认赋值运算符、取址运算符和取址运算符const
1: class Empty
2:
3: {
4:
5: public:
6:
7: Empty(); // 缺省构造函数
8:
9: Empty( const Empty& ); // 拷贝构造函数
10:
11: ~Empty(); // 析构函数
12:
13: Empty& operator=( const Empty& ); // 赋值运算符
14:
15: Empty* operator&(); // 取址运算符
16:
17: const Empty* operator&() const; // 取址运算符 const
18:
19: };
C++默认生成的构造函数,只要在被需要的时候,才会产生。即当我们定义一个类,而不创建类的对象时,就不会创建类的构造函数,析构函数等。
重载、重写、隐藏区别
重载
同一作用域中,同名函数形式参数不同(参数个数、类型、顺序不同),构成函数重载。
注意:
- 函数返回值类型与构成重载无任何关系
- 类的静态成员函数与普通成员函数可以形成重载
- 函数重载发生在同一作用域,如类成员之间的重载,全局函数之间的重载
- C++不能被重载的运算符:"."(成员访问运算符),"*"(成员指针访问运算符),"::"(作用域运算符),sizeof,?:(条件运算符)【前两个运算符不能被重载时为了保证访问成员的功能不变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征】
class A{
public:
void test(int i);
void test(double i); //overload
void test(int i,double j); //overload
void test(double i,int j);//overload
int test(int i); //错误,非重载。重载不关心函数返回类型
};
隐藏
不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数
- 子类函数与父类名称相同,但是参数不同,父类函数被隐藏
- 子类函数与父类函数名称相同,参数相同,但是父类函数没有virutal,父类函数被隐藏
- 子类函数与父类函数名称相同,参数相同,但是父类函数有virutal,父类函数被覆盖
#include<iostream>
using namespace std;
class Base{
public:
void fun(double,int){
cout<<"Base::fun(double,int)"<<endl;
}
};
class Derive :public Base
{
public:
void fun(int) {
cout<<"Derive::fun(int)"<<endl;
}
};
int main()
{
Derive pd;
pd.fun(1);//Derive::fun(int),父类函数被隐藏
//pd.fun(0.01,1);//父类函数被隐藏,error:"Derive::fun":函数不接受两个参数
Base *fd = &pd;
fd->fun(1.0,1);//Base::fun(double,int);
fd->fun(1);//error:no matching function for call to "Base::fun(int)"
return 0;
}
覆盖
子类覆盖父类函数
- 分别位于子类和父类中
- 函数名字与参数相同
- 父类函数是虚函数(virtual)
#include<iostream>
using namespace std;
class Base{
public:
virtual void f(float x){
cout<<"Base::f(float)"<<x<<endl;
}
void g(float x){
cout<<"Base::g(float)"<<x<<endl;
}
void h(float x){
cout<<"Base::h(float)"<<x<<endl;
}
};
class Derived:public Base
{
public:
virtual void f(float x){
cout<<"Derived::f(float)"<<x<<endl;
}
void g(int x){
cout<<"Derived::g(int)"<<x<<endl;
}
void h(float x){
cout<<"Derived::h(float)"<<x<<endl;
}
};
int main()
{
Derived d;
Base *pb = &d;
Derived *fd = &d;
//Good:behavior depends solely on thpe of the object
pb->f(3.14f);//Derived::f(float) 3.14
fd->f(3.14f);//Derived::f(float) 3.14
//Bad:behavior depends on type of the pointer
pb->g(3.14f);//Base::g(float)3.14
fd->g(3.14f);//Derived::g(int) 3
//Bad:behavior depends on type of the pointer
pb->h(3.14f);//Base::h(float) 3.14
fd->h(3.14f);//Derived::h(float) 3.14
return 0;
}
vector使用注意事项
- 当程序重复之星一段代码时,之前保存数据用的vector需要清空
- 清空vector数据时,如果保存的数据项是指针类型,需要逐项delete,否则会造成内存泄漏
- 当插入或删除元素后,注意避免操作迭代器,因为迭代器发生变化,已经失效
map、set、unordered_map、unordered_set ,multimap,multiset
map的insert方法会忽略重复key值,而不是替换,[]方法会对已经存在的key值对应的value进行覆盖
map的插入方法
map<int, string> mapStu;
//插入方式1
mapStu.insert(pair<int, string>(3,"abcd"));
//插入方式2
mapStu.insert(map<int, string>::value_type(1,"dfgh"));
//插入方式3
//这种方法非常直观,但是存在一个性能问题。插入2时,先在mapStu中查找主键为3的项,若没发现,则将
//一个键为3,值为初始化的对组插入到mapStu中,然后再将值修改为"asdf"。若发现已存在3这个键,则
//修改这个键对应的value
mapStu[2] = "asdf";
//输出方式1
for(auto it = mapStu.begin();it!=mapStu.end();++it)
cout<<(*it).first<<" "<<(*it).second<<endl;
//输出方式2
for(auto it = mapStu.begin();it!=mapStu.end();++it)
cout<<it->first<<" "<<it->second<<endl;
map<T1, T2,less<T1>> mapA;//该容器是按照键的升序方式排列元素
map<T1, T2,greater<T1>> mapB;//该容器是按键的降序方式排序
//返回键值大于等于key的第一个元素
lower_bound(key);
//返回键值大于key的第一个元素
upper_bound(key);
//返回键值等于key的元素区间
equal_range(key);
map支持[]操作符,multimap不支持[]操作符
map和set的底层实现为红黑树
unordered_map和unordered_set底层实现为哈希表
string
IO操作
使用cin读取字符串时,遇到空白就停止读取,比如程序输入的是
" hello world"
那么得到的字符串是"hello",前面的空白没了,后面的world也读不出来
如果想要把整个hello world读进来,应该定义两个字符串,分别进行读取
cin>>s1>>s2;
hello存在s1里,world存在s2里。
也可以使用getline方法获取一整行内容
string s;
getline(cin, s);
list
list是双向链表
forward_list是单向链表
list容器不能调用algorightm下的sort函数进行排序,因为sort函数要求容器必须可以随机存储,而list做不到,因此list有自己的排序函数。
list<int> l = {2,3,5,5,6,7,89,0};
for(auto it = l.begin();it!=l.end();++it)
{
cout<<*it<<' ';
}
cout<<endl;
l.sort();
for(auto it = l.begin();it!=l.end();++it)
{
cout<<*it<<' ';
}
cout<<endl;
C++11新特性
nullptr
nullptr 出现的目的是为了替代 NULL。
在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这