C++练级之路——类和对象(下)

2024-04-20 16:44
文章标签 c++ 对象 练级

本文主要是介绍C++练级之路——类和对象(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1、构造函数初始化列表

2、类型转换

3、explicit关键字

4、static成员

5、友元

友元函数

友元类

6、内部类

7、匿名对象

8、拷贝构造时的一些编译器优化

差不多结束了,类和对象!


1、构造函数初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分割的数据成员列表,每个成员变量后面跟着一个放在括号中的初始值或表达式

例如:

typedef int DataType;
class Stack
{
public:Stack(int capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};
class MyQueue
{
public://初始化列表MyQueue(int n = 20):_pushst(n),_popst(n),_size(0){}
//private:Stack _pushst;Stack _popst;int _size;
};
int main()
{MyQueue q1(20);q1._pushst.Push(1);q1._pushst.Push(2);q1._pushst.Push(3);q1._pushst.Push(4);return 0;
}

注意:

1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2.类中包含以下成员,必须放在初始化列表中进行初始化:

引用成员变量

const成员变量

自定义成员变量(且该类没有默认构造函数时)

class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_aobj(a),_ref(ref),_n(10){}
private:A _aobj;  // 没有默认构造函数int& _ref;  // 引用const int _n; // const 
};

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

class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout<<_a1<<" "<<_a2<<endl;}
private:int _a2;int _a1;
};
int main() {A aa(1);aa.Print();
}
A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

 选D

4.初始化列表,不管你写不写,每个成员变量都会先走一遍,自定义类型会调用默认构造(没有默认构造就报错)内置类型有缺省值就用缺省值,没有缺省值,不确定,C++没有规定,要看编译器,先走初始化列表,再走函数题

实践中:尽可能使用初始化列表初始化,不方便再用函数体初始化;

缺省参数还可以这样写:
 

class BB
{
public:
BB()
{ }
private:
int _a=1;
int*ptr=(int*)malloc(40);
Stack _s1=10;
A _a1=20;
A _a2={1,2};};

这里的缺省参数只是声明,最后是给初始化列表进行初始化;初始化列表不写,其实编译器也会自动生成。 

2、类型转换

 class A
{
public://explicitA(int a)A(int a):_a(a){cout << "A(int a)" << endl;}A(int a1, int a2):_a(0),_a1(a1),_a2(a2){}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}private:int _a;int _a1;int _a2;
};int main()
{A aa1(1);// 拷贝构造A aa2 = aa1;// 隐式类型转换// 内置类型转换为自定义类型// 3构造一个A的临时对象,在用这个临时对象拷贝构造aa3// 编译器遇到直接构造+拷贝构造->优化为直接构造A aa3 = 3;// raa 引用的是类型转换中用3构造的临时对象 const A& raa = 3;A aaa1(1, 2);A aaa2 = { 1, 2 };const A& aaa3 = { 1, 2 };return 0;
}

当我们直接用 A aa3=3;中间发生了类型转换,3首先构造A的一个临时对象,再用这个临时对象拷贝构造aa3;

当编译器遇到连续构造+拷贝构造,会优化成直接构造 

const A& raa = 3;

raa引用的是3的临时变量,因为临时变量具有常性,所以要加const;

3、explicit关键字

构造函数不仅可以构造初始化对象,对于单个参数或者除第一个参数没有默认值其余均有默认值的构造函数,还具有类型转换的功能。

用explicit修饰构造函数则不能发生类型转换,限制了类型转换

类型转换是一个很好用,很神奇,比较难理解的一点,以后继续慢慢了解!

4、static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量称为静态成员变量,用static修饰的成员函数称为静态成员函数;

静态成员变量要在类外进行初始化:

 

 static成员变量不在类中创建,而是在静态区,当然static也不能给缺省值,因为缺省值是要进初始化列表的,而static不在初始化列表中进行初始化,所以不能给缺省值,static属于整个类域,在整个类中都可以访问,如果static定义的是public,那么在类外也可以访问;

static修饰成员函数,静态成员函数没有隐藏的this指针,意味着只能访问静态成员变量

kimi.ai的回答:

在C++中,`static`关键字可以用于修饰成员函数,使其成为静态成员函数。静态成员函数具有以下特点:

1. **类函数**:静态成员函数是类的一部分,但它们不依赖于类的任何特定对象实例。

2. **无this指针**:由于静态成员函数不与特定的对象实例相关联,它们在内部实现时没有`this`指针。

3. **类作用域**:静态成员函数只能在类的内部定义,并且它们可以访问类的静态成员,但不能访问非静态成员。

4. **调用方式**:静态成员函数可以通过类名直接调用,而不需要创建类的实例。

5. **不能被虚函数覆盖**:静态成员函数不能被声明为虚函数,因为它们不涉及运行时多态。

6. **存储位置**:静态成员函数的实现代码存储在程序的只读数据段(通常称为文本段)中,与类的其他静态成员一样。

下面是一个简单的C++类,其中包含了一个静态成员函数的例子:

class MyClass {
public:static void staticFunction() {// 静态成员函数的实现// 可以访问静态成员,但不能访问非静态成员}// 非静态成员函数void nonStaticFunction() {// 可以访问静态成员和非静态成员}// 静态成员变量static int staticVar;// 非静态成员变量int nonStaticVar;
};// 定义静态成员变量
int MyClass::staticVar = 0;int main() {// 直接通过类名调用静态成员函数MyClass::staticFunction();// 也可以通过对象调用静态成员函数,但这通常不是推荐的做法MyClass obj;obj.staticFunction();return 0;
}

在这个例子中,`staticFunction`是一个静态成员函数,它可以通过`MyClass::staticFunction()`直接调用,也可以通过对象实例调用,尽管后者不是推荐的做法。静态成员函数非常适合那些不需要访问对象状态的函数,例如工具函数或计算类。

5、友元

友元分为友元函数和友元类

友元函数

友元函数可以访问类中的私有和保护成员,友元函数不是类的成员,所以不会通过this访问类的成员,友元函数的的实现是在类中加一个friend关键字,然后声明;

class MyClass {
private:int privateVar;public:MyClass(int value) : privateVar(value) {}// 声明一个友元函数friend void accessPrivateVar(MyClass& obj);
};// 实现友元函数
void accessPrivateVar(MyClass& obj) {// 访问MyClass的私有成员int value = obj.privateVar;
}int main() {MyClass obj(10);accessPrivateVar(obj);  // 正确,友元函数可以访问私有成员return 0;
}

注意:

1.友元函数可以在类中的任意位置声明;

2.友元函数不能用const修饰;

3.一个函数可以是多个类的友元函数;

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的成员。

class FriendClass;class MyClass {
private:int privateVar;public:MyClass(int value) : privateVar(value) {}// 声明一个友元类friend class FriendClass;
};class FriendClass {
public:void accessPrivateVar(MyClass& obj) {// 访问MyClass的私有成员int value = obj.privateVar;}
};int main() {MyClass obj(10);FriendClass friendObj;friendObj.accessPrivateVar(obj);  // 正确,友元类可以访问私有成员return 0;
}

 注意:

1.友元关系是单向的,不具有交换性;

2.友元关系不能传递,如果C是B的友元,B是A的友元,不能说明C是A的友元;

3.友元关系不能继承(后续会讲)

6、内部类

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

注意:内部类就是外部类的友元

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

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

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

class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A& a){cout << k << endl;//OKcout << a.h << endl;//OK}};
};
int A::k = 1;
int main()
{A::B b;b.foo(A());//A()是一个匿名对象,然后const A& a是A()的引用,//相当于延长了A()的生命周期,当foo函数结束时,A()也就析构了return 0;
}

 

7、匿名对象

class A
{
public:A(int a = 0):_a(10){cout << "A(int a = 0)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A();return 0;
}

A()就是个匿名对象,顾名思义,匿名对象没有名字,但他的生命周期只有这一行,我们可以看到他下一行就会调用析构函数;匿名对象的用途我们以后还会说,这里暂时用不到;

 

8、拷贝构造时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还
是非常有用的。
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}
A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};
void f1(A aa)
{}
A f2()
{A aa;return aa;
}
int main()
{// 传值传参A aa1;f1(aa1);cout << endl;// 传值返回f2();cout << endl;// 隐式类型,连续构造+拷贝构造->优化为直接构造f1(1);// 一个表达式中,连续构造+拷贝构造->优化为一个构造f1(A(2));cout << endl;// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造A aa2 = f2();cout << endl;// 一个表达式中,连续拷贝构造+赋值重载->无法优化aa1 = f2();cout << endl;return 0;
}

这些优化只是编译器的优化,不同的编译器优化的效果也不同,这里我们可以了解一下!

差不多结束了,类和对象!

这篇关于C++练级之路——类和对象(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920794

相关文章

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat