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; }
这些优化只是编译器的优化,不同的编译器优化的效果也不同,这里我们可以了解一下!