C/C++ 笔记 (2)

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 =10const 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题

  1. 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.面向过程与面向对象
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的各个步骤中的行为。面向对象是以功能来划分问题,而不是步骤。
一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。把相关的数据和方法组织为一个整体,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值