1.对于const vector< T >,容器不能增删元素,且元素本身的值不能被更改。
对于const vector<T*>,容器不能增删元素,且元素不能改指其他对象,但是元素当前所指的元素的值可以被改变。
vector< const T > Image;编译器不允许此种行为。
经过注释的语句均为编译不通过的语句。
class A
{
public:
int a;
A():a(0) {}
~A() {}
};
void fun(const vector<A>& a, const vector<A*> b, const vector<int> aInt, const vector<int*> aIntP)
{
//a[0].a = 1;
//a.pop_back();
(*(b[0])).a = 1;
A c;
//b[0] = c;
//b.pop_back();
//aInt[0] = 1;
//aInt.pop_back();
*aIntP[0] = 2;
//aIntP.pop_back();
int d;
//aIntP[0] = &d;
}
int main(int argc, char** argv)
{
A m, n;
int a = 0, b = 0;
vector<A> test1 = { m,n };
vector<A*> test2 = { &m,&n };
vector<int> test3 = { a,b };
vector<int*> test4 = { &a,&b };
fun(test1, test2, test3, test4);
//vector<const Mat> Image;
//编译报错: C2338 The C++ Standard forbids containers of const elements because allocator<const T> is ill-formed.
return 0;
}
//同一个变量不能有多处定义,所以不能放在 PreProcess.h 头文件,
//要么加上extern,要么只在用到此变量的.cpp文件里给出定义。
//加了extern之后,在PreProcess.cpp和main.cpp中的const常量是同一个变量,因为二者地址相同。
extern const int L;
extern const int R;
//const常量在头文件里定义,但是在PreProcess.cpp和main.cpp中的const常量并不是同一个变量,因为二者地址不同,
//两个.cpp文件各自拥有一个const常量。
const int StepNumber =10;
const int PhaseSize = 255;
2.vector< Mat >作为函数参数,按值传递时,Mat的复制方式是浅复制,即修改Mat会影响实参。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using std::vector;
int test(vector<Mat> data)
{
for (int i = 0; i < data.size(); i++)
data[i] = data[i] + 5;
return 0;
}
int main(int argc, char** argv)
{
Mat a(5, 5, CV_8UC1, Scalar(1));
Mat b(5, 5, CV_8UC1, Scalar(2));
Mat c(5, 5, CV_8UC1, Scalar(3));
Mat d(5, 5, CV_8UC1, Scalar(4));
vector<Mat> data;
data.push_back(a);
data.push_back(b);
data.push_back(c);
data.push_back(d);
test(data);
for (int i = 0; i < data.size(); i++)
std::cout << data[i]<<std::endl;
return 0;
}
3.vector.size()虽然默认为0,但是其类型为size_type,是无符号整型,因此vector.size()-1,转化为无符号数,是一个较大的值,用于for循环中,会导致循环次数超出预期。
#include <opencv2/opencv.hpp>
using namespace cv;
using std::vector;
using std::cout;
using std::endl;
int main()
{
vector<int> temp;
cout << temp.size() << endl;
for (int b = 2; b < temp.size() - 6; b++)
cout << b << " "<<"temp.size():"<<temp.size()<<" ";
return 0;
}
4.1 sizeof()的返回类型也是无符号数(size_t, 即unsigned int)。当sizeof的操作数是个类型名时,两边必须加上括号(这常常使人误以为它是个函数),但操作数如果是变量则不必加括号。
4.2 if(x>>4)是不是表示“x远大于4”?
4.3
int i;
i = 1, 2;
int j = (1, 2);
cout << i << " " << j;
4.4 char * const *(*next)(); 所代表的含义。
(<<C专家编程>> 3.3优先级规则)
next是一个指针,它指向一个函数,该函数返回另一个指针,该指针是常量指针,它指向一个类型为 char的指针。
4.5
int a[5] = { 1,2,3,4,5 },i=2;
int* p = a + i;
cout << *p << endl;
4.6 对于具有十个元素的数组a,a[6]与6[a]等价,*a=5等价于a[0]=5.
int a[2] = { 1,2 };
*a = 5;
cout << a[0] << " " << 0[a] << endl;
1[a] = 7;
cout << a[1] << endl;
a[1] = 9;
cout << 1[a] << endl;
4.7可以用数组名初始化指针数组,用数组地址初始化数组指针。
int a[2] = { 1,2 };
int b[3] = { 3,4,5 };
int c[4] = { 6,7,8,9 };
int* d[5] = { a,b,c};
cout << (d)[0][0] << endl;
int(*e)[2] = &a;
cout << (*e)[1] << endl;
int(*f)[3] = {&b};
cout << (*f)[2] << endl;
int(*g)[4] = &c;
cout << (*g)[3] << endl;
cout << g[0][3] << endl;//g[0] -> *(g+0)
4.8 对于引用 a[m][n],a可能有多种声明形式。
int a[1][2] = { 0,1 };
int t[2] = { 3,4 };
int* b[1] = { t };
int p[2] = { 5,6 };
int* k = p;
int** c = &k;
int m[2] = { 7,8 };
int(*d)[2] = &m;
cout << a[0][0] << " " << b[0][0] << " " << c[0][0] << " " << d[0][0] << " " << (*d)[0] << endl;
4.9 不能直接用二维数组初始化二级指针,需要把二维数组用指针数组表示,然后用指针数组初始化二级指针。
(https://blog.csdn.net/yongheng_1999/article/details/52765130)
int t[2] = { 3,4 };
int* b[1] = { t };
int h[1][1] = { 10 };
int** c = b;
//c = h; //会编译出错
int* d[1] = { h[0] };
int** e = d;
cout << b[0][0] << " " << c[0][0] << " " << e[0][0] << endl;
int ab[][2] = { 1,2 };
cout << &ab << " " << ab << " " << *ab << " " << &ab[0] << " " << ab[0] << " "
<< *ab[0] << " " << ab[0][0] << endl;
4.10 以下代码属于未定义行为(VS2019,输出结果为 (null) )。
char* p = nullptr;
printf("%s\n", p);
5
5.1 在循环体里声明的局部变量,在循环结束之后,其作用周期结束。但是分配给变量的内存仍然存在,不会被立即回收。
int* m;
int pp = 1;
while (pp < 2)
{
int a = 2;
m = &a;
cout << &a << endl;
++pp;
}
cout << m << endl;
cout << *m << endl;
5.2 使用new为变量申请内存,在该变量作用周期结束之后,要用delete回收内存。并且,把指针指向nullptr.
5.3 stoi()函数将数字字符串(是字符串不是字符)转换为int类型,转换时会做范围检查,超出int类型的范围则会报错。
5.4 isalpha()、isdigit()对字符进行操作,判断参数是否是字母或数字。
5.5 char s; s-48等价于s-'0'
;
5.6 若result为"0200",欲得到"200",下述语句不能实现前述功能
string result="0200";
for(char r:result)
{
cout<<(int)r<<endl;
cout<<result<<endl;
if(r=='0')
result.erase(0,1);
else
break;
}
需修改为:
for(int m=0;m<(int)result.size();m++)
{
if(result[m]=='0')
{
result.erase(0,1);
m--;
}
else
break;
}
6
6.1 string类的append操作,将字符串附加到指定字符串之前或之后
result=st.top()+result;//表示将result附加到st.top()之后
result=result+st.top();//表示将st.top()附加到result之后
6.2 string,vector均有back()成员函数,返回容器尾部元素的引用。
6.3 vector的emplace_back()成员函数,功能和push_back()类似,但是前者不进行拷贝操作,因此效率较高。
6.4 string的find()成员函数,用于查找元素,查找成功则返回目标元素的下标值,否则返回string::npos(一个特殊的值,其值为string.size())。
vector没有find函数,所以在写一些判断的时候就需要用到泛型find。(link)
6.5
string s;
s="abc";
s=s+"\";//这样会导致\后的元素被认为是转义字符
s=s+"\\";//可这样写
//为了防止s[i+1]访问越界,可以在s末尾添加一个元素,例如1541题
- 堆
7.1 STL容器组件里没有堆,有相应的堆泛型算法,push_heap(), pop_heap(), sort_heap(), make_heap().
STL中堆对应的容器是priority_heap(一般归类为container adapter, 容器配接器,而非被归类为容器), 其以底部容器(缺省情况下是vector)为根据,加上上述的heap处理规则来实现。
7.2 erase()函数
vector<int> a={1,2,3,4};
vector<int>::iterator it=a.begin();
//执行此语句后,it的指向是不确定的。
a.erase(it);
//执行此语句后,it指向所移除的元素的下一个元素。
it=a.erase(it);
7.3 两个同类型迭代器可以直接相减。
7.4 vector与priority_queue的自定义优先级
//vector
class Solution {
private:
bool static compare(const pair<char,int>& a, const pair<char,int>& b)
{
return a.second>b.second;
//按照定义的优先级,***元素从大到小排序***
}
public:
string reorganizeString(string S) {
unordered_map<char,int> ChMap;
int MaxFrequence=0;
for(char ch:S)
{
ChMap[ch]++;
MaxFrequence=(MaxFrequence>ChMap[ch])?MaxFrequence:ChMap[ch];
}
if(MaxFrequence> (((int)S.size()+1)/2) )
return "";
vector<pair<char,int> > ChVector(ChMap.begin(),ChMap.end());
//比较函数必须为static函数
sort(ChVector.begin(),ChVector.end(),compare);
string result(S);
int num=0;
int index=0,IndexVector=0;
while(num<(int)result.size())
{
result[index]=ChVector[IndexVector].first;
ChVector[IndexVector].second--;
index+=2;
if(index>=(int)result.size())
index=1;
if(ChVector[IndexVector].second==0)
IndexVector++;
num++;
}
return result;
}
};
//priority_queue
class Solution {
private:
struct compare
{
bool operator() (const pair<int,pair<int,int>>& a, const pair<int,pair<int, int>>& b)
{
return (a.first+a.second.first)>(b.first+b.second.first);
//元素按照所定义的优先级,***从小到大排序***
}
};
public:
vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
priority_queue< pair<int,pair<int,int> >, vector<pair<int,pair<int,int>> >, compare > pq;
int length1=(int)nums1.size();
int length2=(int)nums2.size();
if(length1==0 || length2==0)//数组可能为空
return vector<vector<int>>();
bool flag=false;//输出结果的顺序是有要求的
if(length1>length2)//算法复杂度正比于nums1的长度
{
swap(nums1,nums2);
swap(length1,length2);
flag=true;
}
for(int n:nums1)
{
pq.push(make_pair(n,make_pair(nums2[0],0)));
}
vector<vector<int>> result;
for(int i=0;i<k && !pq.empty();i++)
{
auto temp=pq.top();
pq.pop();
vector<int> Temp;
if(flag==true)//输出结果的顺序是有要求的
Temp={temp.second.first,temp.first};
else
Temp={temp.first,temp.second.first};
result.emplace_back(Temp);
if(temp.second.second<length2-1)//此处变量应是小于length2-1,而非length2
{
temp.second.second++;
pq.push(make_pair(temp.first,make_pair(nums2[temp.second.second],temp.second.second)));
//巧妙的算法
}
}
return result;
}
};
7.5 unordered_map中的键值对是不允许被用户手动修改的
#include <unordered_map>
#include <iostream>
#include <vector>
int main(int argc, char** argv)
{
std::unordered_map<int, int> map;//数组元素值,对应的下标
std::vector<int> nums = { 1,2,3,4,5,1 };
for (int i = 0; i < (int)nums.size(); i++)
{
//map.emplace(nums[i],i); // unordered_map中的键值对是不允许被用户手动修改的
map[nums[i]] = i;
std::cout << map[nums[i]] << std::endl;
}
return 0;
}
8.1 cin、cout与scanf、printf
std::ios_base::sync_with_stdio(true)
函数默认参数为true,这样可以混合使用iostream流和stdio流,而不会出现数据输入输出的问题。但是,效率会降低。
(http://www.cplusplus.com/reference/ios/ios_base/sync_with_stdio/)
8.2 对于浮点数,C++默认的显示位数为6位(整数位加小数位),但是显示位数和存储精度无关。可通过cout.precision(number);来更改显示位数。
8.3
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char** argv)
{
int a = 8;
float b = 8.0f;
float c = 3.14159;
float d = 3.14159;
float e = 3.1416;
if (a == b)
cout << "ab_cool.\n";
else
cout << "ab_uncool.\n";
if (c == d)
cout << "cd_cool.\n";
else
cout << "cd_uncool.\n";
if (e == (c+0.00001))
cout << "ce_cool.\n";
else
cout << "ce_uncool.\n";
if (3.1416 == (3.14159 + 0.00001))
cout << "cool.\n";
else
cout << "uncool.\n";
return 0;
}
8.4 若编译器坚持必须在编译期间知道数组的大小,可用"the enum hack"补偿做法(<<Effective C++>> P15).
枚举值是常量,定义之后不能再通过赋值被改变。
enum {Number=5};
int scores[Number];
9.之所以使用虚函数,是因为需要在信息不全的情况下进行多态运行。虚函数调用是在部分信息下完成工作的机制,允许我们只知道接口而不知道对象的确切类型。而构造函数是用来初始化实例的,实例的类型必须是明确的。 因此,构造函数没有必要被声明为虚函数。
C++多态分为静态多态和动态多态。
静态多态:也称为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用的函数,如果有对应的函数就调用该函数,否则出现编译错误。
静态多态有两种实现方式:
函数重载:包括普通函数的重载和成员函数的重载;
函数模板的使用。
动态多态:有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式。
10.1
int i = 4;
int arr[5] = { 0 };
int* ptr = arr;
struct S { double d; }s;
void Overloaded(int) {};
void Overloaded(char) {};//重载的函数
int&& RvalRef() { return 5; };
const bool Func(int) { return true; };
//规则一:推导为其类型
decltype (arr) var1; //int 标记符表达式
decltype (ptr) var2;//int * 标记符表达式
decltype(s.d) var3;//doubel 成员访问表达式
//decltype(Overloaded) var4;//重载函数。编译错误。
//规则二:将亡值。推导为类型的右值引用。
decltype (RvalRef()) var5 = 1;
//规则三:左值,推导为类型的引用。
decltype ((i))var6 = i; //int&
decltype (true ? i : i) var7 = i; //int& 条件表达式返回左值。
decltype (++i) var8 = i; //int& ++i返回i的左值。
decltype(arr[5]) var9 = i;//int&. []操作返回左值
decltype(*ptr)var10 = i;//int& *操作返回左值
decltype("hello")var11 = "hello"; //const char(&)[6] 字符串字面常量为左值,且为const左值。
//规则四:以上都不是,则推导为本类型
decltype(1) var12;//const int
decltype(Func(1)) var13 = true;//bool,Func()函数返回类型为const bool
decltype(i++) var14 = i;//int i++返回右值
const int aa=1;
decltype(aa) var22 = i;//const int
int main()
{
var13 = false;
//var22 = 0;
return 0;
}
10.2 Static成员函数
http://c.biancheng.net/view/2228.html
10.3 在main()函数执行前执行
C/C++并非必须从main()函数开始执行。
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "construct of the Test" << endl;
}
~Test()
{
cout << "deconstruct of the Test" << endl;
}
};
Test a;
int main()
{
cout << "main start" << endl;
Test b;
return 0;
}
10.4
以下四行代码的区别?
const char * arr = "123";
/*字符串123保存在常量区,const本来是修饰arr指向的值不能通过
arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样*/
char * brr = "123"; //编译失败,const char*不能赋给char*类型
const char crr[] = "123";
//数组将123复制到栈区,编译器也可能作出优化,将其放到常量区
char drr[] = "123";
// //数组将123复制到栈区,可以通过drr去修改,字符串“123”一直在常量区
10.5
对于局部常量,存放在栈区;对于全局常量,编译器一般不分配内存,放在符号表中以提高访问效率;字面值常量,比如字符串,放在常量区。
10.6
1.重写/覆盖:派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
2. 重载:同一作用域中,名称相同的函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载,不要求返回类型相同。
3. 隐藏:不同作用域中,定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。
在派生类与基类中,同名的函数,若参数类型相同,且基类函数不是虚函数,则构成隐藏。若两个同名函数参数类型不同,则无论基类函数是不是虚函数,都会构成隐藏。
using Base::f; 会把基类中函数名为f的函数加入到派生类中。否则,派生类若要使用基类的两个函数,则需要在派生类中进行定义。
#include <iostream>
using namespace std;
class Base {
public:
bool f() {
cout << "f()" << endl;
return true;
}
void f(int n) {
cout << "Base::f(int)" << endl;
}
};
class Derived : public Base {
public:
using Base::f;
void f(int n) {
cout << "Derived::f(int)" << endl;
}
};
int main()
{
Base b;
Derived d;
d.f();
d.f(1);
return 0;
}
10.7 new/delete与malloc/free
前者是为了便于面向对象而设计的,基于后者而实现。对于自定义类型,使用new/delete时会调用相应的构造函数和析构函数。
区别
delete [] 与 delete
10.8 移动构造
有些时候,比如我们用 s1 初始化 s2 之后,s1 不再需要了,这时候进行深拷贝就非常的浪费时间浪费内存了,可以直接将 s1 的资源交给 s2 ,这时,移动构造就派上用场了。
10.9 仅当容器元素存储在连续内存空间里,迭代器才可以加上或减去一个整数,如vector.
若容器元素不是存储在连续内存中,则不支持上述操作,如list. 但是迭代器支持自增、自减操作。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
if(root==nullptr)
return vector<int>();
stack<TreeNode*>st;
TreeNode* node=root;
st.push(node);
while(!st.empty())
{
node=st.top();
st.pop();
if(node==nullptr)
continue;
result.emplace_back(node->val);
st.push(node->left);
st.push(node->right);
}
//翻转vector数组
auto RBegin=result.begin();
auto REnd=result.end();
REnd--;
while(RBegin!=REnd && RBegin!=(REnd+1) )//必须是&&
//迭代器可以RBegin+i 或RBegin-i
{
int temp=0;
temp=*RBegin;
*RBegin=*REnd;
*REnd=temp;
RBegin++;
REnd--;
}
return result;
}
private:
vector<int> result;
};
10.10
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d\n", *(a + 1), *(ptr - 1));
int* p = (int*)(a + 1);
cout << *p << endl;
int* pp = (a + 1);
cout << *pp << endl;
11.类对象的构造和析构顺序
下例中,构造与析构函数必须是public成员,注意访问权限。
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A() { cout << "Con A" << endl; }
~A() { cout << "Des A" << endl; }
};
class B
{
public:
A a;
B() { cout << "Con B" << endl; }
~B() { cout << "Des B" << endl; }
};
class C:public B
{
public:
C() { cout << "Con C" << endl; }
~C() { cout << "Des C" << endl; }
};
int main()
{
C c;
return 0;
}
12.派生类如果继承了一个有虚函数的基类,sizeof一下是多大?继承了两个呢?
32位系统下,答案分别是4、8。应该是派生类继承了两个虚函数表,所以有两个虚函数指针。
13. 递归函数返回值为空,可以节省空间。因为每一层的递归,都要为返回值分配空间。
14.若函数执行开销小于调用函数的开销,则最好不要再另外写一个新函数。
15.基类的析构函数应为虚函数。
如下,因为基类的析构函数不是虚函数,所以程序输出为4 1 .
若基类的析构函数是虚函数,则程序输出为4 3 1. 即先调用派生类析构函数,再调用基类析构函数。与构造顺序相反。
#include<iostream>
#include<stdio.h>
using namespace std;
class A
{
public:
A() { }
~A() {cout<<"1"<<endl;}
virtual void DO(){cout<<"2"<<endl;}
};
class B: public A
{
public:
B() { }
~B() {cout<<"3"<<endl;}
virtual void DO(){cout<<"4"<<endl;}
};
int main()
{
A *PA = new B();
PA->DO();
delete PA;
return 0;
}
16.有两个库有相同的两个函数,在引用两个库时如何解决冲突。
1)显式加载库函数
2)利用命名空间
3)开启三个进程,一个主程序,另外两个程序分别使用两个库,主程序开始后开启两个线程调度其它两个程序
(link)(link)
17.面向对象的特征:抽象、封装、继承、多态
面向对象:从宏观上去操作对象,从总体上把握对象的特征,而不必专于对象的细节。
抽象:将一类对象的共同特征总结出来,包括数据抽象和行为抽象两方面。
封装:把数据和操作数据的方法隐藏起来,对数据的访问只能通过已定义的接口。封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
继承:实现对父类的复用,但是也具有自己的特点。
多态:允许对象对同一个事件做出不同的响应,包括静态多态、动态多态。
18.面向过程与面向对象
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的各个步骤中的行为。面向对象是以功能来划分问题,而不是步骤。
一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。把相关的数据和方法组织为一个整体,从更高的层次来进行系统建模,更贴近事物的自然运行模式。