目录
第一节:友元
1-1.友元函数
1-2.友元类
第二节:匿名对象
第三节:编译器优化
3-1.构造+拷贝->直接构造
3-2.构造+拷贝+拷贝->直接构造
第四节:new
下期预告:
第一节:友元
一个类可以有友元函数和友元类,我们一个一个的介绍它们的特性。
1-1.友元函数
一个类声明友元函数的案例如下:
class Test
{
friend void Print(Test* t); // 声明在内
private:
int a = 1;
};
void Print(Test* t) // 定义在外
{
std::cout << t->a << std::endl;
}
它的特点是能够访问、调用这个类中的private成员,比如用 Print 函数打印私有变量a:
int main()
{
Test t;
Print(&t);
}
它的用途:
一个类的成员函数的第一个参数是this指针,此时我们用operator重载第一个参数不是类类型的时候就会报错,operator重载函数也不能用static修饰,此时就需要使用友元函数,例如重载 <<:
class Test
{
friend std::ostream& operator<<(std::ostream& stream,const Test& t); // 声明在内
private:
int a = 1;
};
std::ostream& operator<<(std::ostream& stream,const Test& t) // 定义在外
{
std::cout << t.a;
return stream;
}
int main()
{
Test t;
std::cout << t << std::endl;
return 0;
}
注意:
1)友元函数需要谨慎使用,因为它破坏了类的封装
2)不能通过类像调用成员函数那样直接调用友元函数
1-2.友元类
一个类有两种方成为另一个类的友元类,第一种是像友元函数那样声明:
class Test
{
public:
friend class test; // 声明类 test 是 Test 的友元
private:
int a = 1;
};
class test
{
private:
int b = 2;
};
第二种是在 Test 中定义 test 类,这样的话 test 是 Test 的友元类(这种友元类又叫内部类):
class Test
{
class test // test 是 Test 的友元
{
private:
int b = 2;
};
private:
int a = 1;
};
一个类的友元类可以访问、调用它自己的私有成员,例如通过 test 类打印 Test 类的私有变量a:
#include <iostream>
class Test
{
friend class test; // 声明类 test 是 Test 的友元
private:
int a = 1;
};
class test
{
public:
void Print(Test& t1)
{
std::cout << t1.a << std::endl; // 打印Test类中的私有变量
}
private:
int b = 2;
};
int main()
{
Test t1;
test t2;
t2.Print(t1);
return 0;
}
注意:
如果A是B的友元类,不代表B是A的友元类,友元关系不是相互的
如果A是B的内部类,那么A天生是B的友元类,且它们的关系是对等的
第二节:匿名对象
匿名对象就是没有名字的对象,它的生命周期只在当前语句,一个匿名函数可以用"类名()"直接创建:
class Test
{
private:
int a = 1;
};
int main()
{
Test t(); // 有名对象
Test(); // 匿名对象
return 0;
}
我们可以用它调用某个类中的成员函数:
#include <iostream>
class Test
{
public:
void Hello()
{
std::cout << "Hello" << std::endl;
}
private:
int a = 1;
};
int main()
{
Test().Hello();
return 0;
}
它在调用完后就会自动销毁,不会长久的占用内存空间。
第三节:编译器优化
在类进行构造时,如果发生了隐式类型转换,编译器可能会进行如下优化:
3-1.构造+拷贝->直接构造
例如:
Test t1 = 2;
正常流程是先用2调用构造函数创建一个匿名对象,然后用这个匿名对象对t1进行拷贝构造。
编译器优化后就变成了用2直接构造t1,即:
Test t1(2);
#include <iostream>
class Test
{
public:
Test(int x): // 初始化构造
a(x)
{
std::cout << "Test(int)" << std::endl;
}
Test(const Test& t) // 拷贝构造
{
a = t.a;
std::cout << "Test(const Test&)" << std::endl;
}
private:
int a;
};
int main()
{
Test t = 2;
return 0;
}
3-2.构造+拷贝+拷贝->直接构造
例如:
Test func()
{
return Test(2);
}
Test t1 = func();
调用func函数后,会先因为Test()构造一个匿名对象,然后返回值没加&,所以返回的是这个匿名对象的一份拷贝,这有需要调用一次拷贝,最后调用拷贝构造给t1初始化。
编译器优化后会跳过各种拷贝,直接用return后面的()里的值给t1构造。
#include <iostream>
class Test
{
public:
Test(int x): // 初始化构造
a(x)
{
std::cout << "Test(int)" << std::endl;
}
Test(const Test& t) // 拷贝构造
{
a = t.a;
std::cout << "Test(const Test&)" << std::endl;
}
private:
int a;
};
Test func()
{
return Test(2);
}
int main()
{
Test t = func();
return 0;
}
第四节:new
new的中文意思是"新的",它的作用是在堆区开辟空间,然后返回它的指针:
类型* 指针 = new 类型;
例如在堆区开辟一个int类型大小的空间:
int* ptr = new int;
如果想开辟一块连续空间,可以加[ ]:
int* ptr = new int[4]; // 空间大小=sizeof int*4
像上面这样开辟空间,空间是没有初始化的,要初始化可以加( ):
int* ptr = new int(10); // 开辟sizeof int大小的空间,初始化成10
int* ptr = new int[4]{1,2,3}; // 开辟 sizeof int*4大小的空间,按顺序初始化成1、2、3,没有写的就默认初始化成0;即{1,2,3,0}.
对于自定义类型,它甚至可以调用它的构造函数进行初始化:
class Test
{
public:
Test(int a):
_a(a)
{}
private:
int _a;
}
Test* ptr = new Test(1);
如果new的过程中出现错误,它会自动抛出异常,不需要用if(ptr == nullptr)进行检查。
C++也可以使用C语言的malloc等函数来在堆区上开辟空间,且达成同样效果,实际上new的底层就是malloc,但是new的可读性更好、使用起来也更方便。
下期预告:
C语言中malloc出来的空间需要用free释放,C++中new出来的空间也需要delete释放,所以下一次将进入delete的介绍,new开辟空间时的特殊处理和new、delete的底层。