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++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

javaSE类和对象进阶用法举例详解

《javaSE类和对象进阶用法举例详解》JavaSE的面向对象编程是软件开发中的基石,它通过类和对象的概念,实现了代码的模块化、可复用性和灵活性,:本文主要介绍javaSE类和对象进阶用法的相关资... 目录前言一、封装1.访问限定符2.包2.1包的概念2.2导入包2.3自定义包2.4常见的包二、stati

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3