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

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

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;//OK
      cout << 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;
}


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

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

相关文章
|
2天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
2天前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
2天前
|
编译器 C++
【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 )
本文探讨了C++中类的成员函数,特别是取地址及const取地址操作符重载,通常无需重载,但展示了如何自定义以适应特定需求。接着讨论了构造函数的重要性,尤其是使用初始化列表来高效地初始化类的成员,包括对象成员、引用和const成员。初始化列表确保在对象创建时正确赋值,并遵循特定的执行顺序。
|
2天前
|
C语言 C++
【C++】日期类Date(详解)③
该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。
|
2天前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
2天前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
2天前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。
|
5天前
|
存储 编译器 C语言
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
10 2
|
4天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
7 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
5天前
|
存储 编译器 C++
【C++ 初阶路】--- 类和对象(下)
【C++ 初阶路】--- 类和对象(下)
7 1