0
点赞
收藏
分享

微信扫一扫

使用QT5和OpenCV实现图像和视频处理软件

彭维盛 2023-05-15 阅读 90

目录

一、初始化列表

1.1 - 定义

1.2 - 使用初始化列表的原因

1.3 - 成员变量的初始化顺序

二、静态成员

2.1 - 静态成员变量

2.2 - 静态成员函数

三、友元

3.1 - 友元函数

3.2 - 友元类

四、内部类

五、匿名对象

5.1 - 匿名对象的特性

5.2 - 匿名对象的使用场景

六、编译器所做的一些优化



一、初始化列表

1.1 - 定义

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式

class Date
{
public:
    // 一、在构造函数体内进行赋值操作
    /*Date(int year = 1949, int month = 10, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }*/
​
    // 二、使用初始化列表
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
private:
    int _year;
    int _month;
    int _day;
};

1.2 - 使用初始化列表的原因

  1. 性能原因。对于内置类型的成员变量,使用初始化列表和在构造函数体内赋值差别不是很大,但是对于类类型的成员变量来说,最好使用初始化列表。例如

    #include <iostream>
    using namespace std;
    ​
    class A
    {
    public:
        // 默认的构造函数
        A(int x = 0) : _i(x)
        {
            cout << "A(int x = 0)" << endl;
        }
    ​
        // 拷贝构造函数
        A(const A& a) : _i(a._i)
        {
            cout << "A(const A& a)" << endl;
        }
    ​
        // 赋值运算符重载
        A& operator=(const A& a)
        {
            cout << "A& operator=(const A& a)" << endl;
            _i = a._i;
            return *this;
        }
    ​
        void Print() const
        {
            cout << _i << endl;
        }
    private:
        int _i;
    };
    ​
    class B
    {
    public:
        // 拷贝构造函数
        B(const A& a)
        {
            _a.Print();
            _a = a;
            _a.Print();
        }
    private:
        A _a;
    };
    ​
    int main()
    {
        A a(10);
        // A(int x = 0)
    ​
        B b(a);
        // A(int x = 0)
        // 0
        // A& operator=(const A& a)
        // 10
        return 0;
    }
    class B
    {
    public:
        B(const A& a) : _a(a) 
        { 
            _a.Print(); 
        }
    private:
        A _a;
    };

    class B
    {
    public:
        B(int x = 0) : _a(x)
        {
            _a.Print();
        }
    private:
        A _a;
    };

  2. 除了性能原因之外,有时候初始化列表是不可或缺的。类包含以下成员变量时,必须放在初始化列表位置进行初始化

    • const 成员变量,因为 const 对象必须初始化。
    • 引用类型的成员变量,因为引用必须初始化。
    • 没有默认构造函数的类类型成员变量,因为使用初始化列表时不必调用默认构造函数来初始化,可以直接调用对应的构造函数来初始化。
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
    	// 带参的构造函数
    	A(int x) : _i(x) { cout << "A(int x)" << endl; }
    private:
    	int _i;
    };
    
    class B
    {
    public:
    	B(int y, int& r, int x)
    		: _j(y), _r(r), _a(x) {}
    private:
    	const int _j;  // const 成员变量
    	int& _r;  // 引用类型的成员变量
    	A _a;  // 没有默认构造函数的类类型的成员变量
    };
    
    int main()
    {
    	int n = 20;
    	B b(10, n, 30);
    	// A(int x)
    	return 0;
    }

1.3 - 成员变量的初始化顺序

成员变量在类中声明的次序就是其在初始化列表中初始化的顺序,与其在初始化列表中的先后次序无关

class A
{
public:
    A(int x = 0) : _j(x), _i(_j) {}
​
    void Print() const
    {
        cout << _i << " " << _j << endl;
    }
private:
    int _i;
    int _j;
};
​
int main()
{
    A a;
    a.Print();  // 随机值 0
    return 0;
}


二、静态成员

2.1 - 静态成员变量

对象的内存中包含了成员变量,不同的对象占用不同的内存,这使得不同对象的成员变量相互独立,它们的值不受其他对象的影响。可是有时候我们希望在多个对象之间共享数据,在对象 a 改变了某份数据后,对象 b 可以检测到。共享数据的典型使用场景是计数

在 C++ 中,可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字 static 修饰。

#include <iostream>
using namespace std;
​
class A
{
public:
    A() { ++_count; }
    A(const A& a) { ++_count; } 
    ~A() { --_count; }
    static int _count;  // 静态成员变量
};
​
int A::_count = 0;
​
int main()
{
    cout << A::_count << endl;  // 0
    A a1;
    A a2;
    A a3(a1);
    A a4(a2);
    cout << A::_count << endl;  // 4
    return 0;
}

这段代码定义了一个类 A,其静态成员变量 _count 用来统计当前创建出来的类对象的个数。

static 成员变量的特性

  1. static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 _count 分配一份内存,所有对象使用的都是这份内存中的数据。

  2. static 成员变量不占用对象内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问,所以 sizeof(A) == 1

  3. static 成员变量既可以通过对象来访问,也可以通过类来访问,例如:A::_count

  4. static 成员变量必须在类的外部初始化,具体形式为

    type class::name = value;
  5. static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在类外初始化时分配,所以没有在类外部初始化的 static 成员变量不能使用

2.2 - 静态成员函数

在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。

编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把调用该函数的对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数

普通成员函数可以访问所有成员(包括成员变量和成员函数),而静态成员函数因为没有 this 指针,不知道指向哪个对象,所以无法访问对象的成员变量,即不能访问普通成员变量,也无法调用普通成员函数,只能访问静态成员变量和调用静态成员函数

#include <iostream>
using namespace std;
​
class A
{
public:
    A() { ++_count; }
    A(const A& a) { ++_count; }
    ~A() { --_count; }
    static int GetCount() { return _count; }  // 静态成员函数
private:
    static int _count;   // 静态成员变量
};
​
int A::_count = 0;
​
int main()
{
    cout << A::GetCount() << endl;  // 0
    A a1;
    A a2;
    A a3(a1);
    A a4(a2);
    cout << A::GetCount() << endl;  // 4
    return 0;
}


三、友元

私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这些固然能带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦。

C++ 设计者认为,如果有的程序员真的非常怕麻烦,就是想在类的成员函数外部直接访问对象的私有成员,那还是做一点妥协以满足他们的愿望为好,这也算是眼前利益和长远利益的折中。因此,C++ 就有了友元(friend) 的概念。打个比方,这相当于是说:朋友是值得信任的,所以可以对他们公开自己的隐私。

友元分为两种:友元函数和友元类

3.1 - 友元函数

在定义一个类的时候,可以把一些函数(包括全局函数和其他类的成员函数)声明为友元,这样那些函数就成为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员了。注意:友元函数可以在类定义的任何地方声明,不受类访问限定符的限制

将全局函数声明为友元的写法如下:

friend 返回值类型 函数名(参数列表);

将其他类的成员函数声明为友元的写法如下:

friend 返回值类型 其他类的类名::成员函数名(参数列表);

但是,不能把其他类的私有成员函数声明为友元

示例

#include <iostream>
using namespace std;
​
class Date
{
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);
public:
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
private:
    int _year;
    int _month;
    int _day;
};
​
// 流插入运算符(<<)重载
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "-" << d._month << "-" << d._day;
    return out;
}
​
// 流提取运算符(>>)重载
istream& operator>>(istream& in, Date& d)
{
    in >> d._year >> d._month >> d._day;
    return in;
}
​
int main()
{
    Date d;
    cout << d << endl;  // 1949-10-1
    cin >> d;  // 假设输入:2023 5 1
    cout << d << endl;  // 2023-5-1
    return 0;
}

3.2 - 友元类

一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有成员。在类定义中声明友元类的写法如下:

friend class 类名;

示例

class Time
{
    friend class Date;
public:
    Time(int hour = 0, int minute = 0, int second = 0)
        : _hour(hour), _minute(minute), _second(second) {}
​
private:
    int _hour;
    int _minute;
    int _second;
};
​
class Date
{
public:
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
​
    void SetTimeOfDate(int hour, int minute, int second)
    {
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};
​
int main()
{
    Date d(2023, 5, 1);
    d.SetTimeOfDate(12, 30, 00);
    return 0;
}


四、内部类

如果一个类定义在另一类的内部,这个内部类就叫作内部类。内部类是一个独立的类,它不属于外部内,更不能通过外部的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

注意:内部类是外部类的友元类,参考友元类的定义,内部类可以通过外部类的对象来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性

  1. 内部类可以定义在外部类的 public、protected、private 都是可以的。

  2. 内部类可以直接访问外部类的 static 成员,不需要外部类的对象/类名。

  3. sizeof(外部类) = 外部类,和内部类没有任何关系。

#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x) {}
    // 内部类
    class B
    {
    public:
        B(int y = 0) : _j(y) {}
        void func(const A& a)
        {
            cout << _s << " " << a._i << endl;
        }
    private:
        int _j;
    };
private:
    static int _s;
    int _i;
};
​
int A::_s = 1;
​
int main()
{
    cout << sizeof(A) << endl;  // 4
​
    A a(10);
    A::B b;
    b.func(a);  // 1 10
    return 0;
}


五、匿名对象

#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x) 
    { 
        cout << "A(int x = 0)" << endl; 
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _i;
};
​
int main()
{
    // 非匿名对象:
    // A a1;
    // A a2(10);
​
    // 匿名对象(顾名思义,即没有名字的对象):
    A();
    A(10);  
    return 0;
}

5.1 - 匿名对象的特性

  1. 匿名对象的生命周期只有它所在的那一行

  2. 匿名对象具有常性

    // A& ra = A(10);  // error(权限放大)
    const A& ra = A(10);  // ok
  3. 使用常引用会延长匿名对象的生命周期

5.2 - 匿名对象的使用场景

  1. 当方法只调用一次的时候就可以使用匿名对象

    #include <iostream>
    using namespace std;
    ​
    class Solution
    {
    public:
        int SumSolution(int n)
        {
            int sum = 0;
            for (int i = 1; i <= n; ++i)
            {
                sum += i;
            }
            return sum;
        }
    };
    ​
    int main()
    {
        cout << Solution().SumSolution(100) << endl;  // 5050
        return 0;
    }
  2. 当作参数进行传递

    #include <iostream>
    using namespace std;
    ​
    class Date
    {
        friend void Display(const Date& d);
    public:
        Date(int year = 1949, int month = 10, int day = 1)
            : _year(year), _month(month), _day(day) {}
    private:
        int _year;
        int _month;
        int _day;
    };
    ​
    void Display(const Date& d)
    {
        cout << d._year << "-" << d._month << "-" << d._day << endl;
    }
    ​
    int main()
    {
        Display(Date(2023, 5, 1));  // 2023-5-1
        return 0;
    }

 


六、编译器所做的一些优化

#include <iostream>
using namespace std;

class A
{
public:
	A(int x = 0) : _i(x) 
	{
		cout << "A(int x = 0)" << endl;
	}

	A(const A& a) : _i(a._i)
	{
		cout << "A(const A& a)" << endl;
	}

	A& operator=(const A& a)
	{
		cout << "A& operator=(const A& a)" << endl;
		if (this != &a)
		{
			_i = a._i;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _i;
};

void func1(A a)
{

}

A func2()
{
	A a;
	return a;
}

int main()
{
	func1(10);  
	// 构造(隐式类型转换)+ 拷贝构造 --> 优化成一个构造
	cout << "------------------" << endl;

	func1(A(10));
	// 构造 + 拷贝构造 --> 优化成一个构造
	cout << "------------------" << endl;

	A ret1 = func2(); 
	// 在一个表达式中,连续的拷贝构造 --> 优化成一个拷贝构造
	cout << "------------------" << endl;

	// A ret2;
	// 1. 构造
	// ret2 = func2();
	// 2. 赋值运算符重载
	// 编译器无法优化
	return 0;
}

举报

相关推荐

0 条评论