自动类型转换和函数模板

自动类型转换和函数模板

自动类型转换函数

首先我们先来分析内置类型,如果两种数据类型是兼容的,在C++ 中可以自动转换,如果要从更大的数转换为更小的数,就可能会发生截断。

long count = 8;//将int 转换为long
double time= 11;//将int转换为 double
int size = 3.33 //将double转换为int的3

从底层角度分析,不同数据类型的差别在于取值范围和精度,数据的取值范围越大,精度越高。

整型从低到高:
char -> short -> int -> long -> long long
浮点型从低到高:
float -> double -> long double

所以数据在进行自动类型转换时 遵循规则如下:

  • 1、如果一个表达式中出现了不同类型操作数的混合运算,较低类型将自动向较高类型转换。
  • 2、赋值运算的右值类型与左值类型不一致时,将右值类型提升/降低为左值类型。
  • 3、赋值运算右值超出了左值类型的表示范围,把该右值截断后赋给左值,所得结果可能毫无意义。
  • 4、当表达式中含有浮点型操作数时,所有操作数都将转换为浮点型。

这种转换工作,是编译器自动完成的,可以隐式的进行,不需要程序员干预。

对应需要程序员干预的,类型转换,也称为强制类型转换(显式转换)。比如下面这个例子。

double rel = (double)10/2; //本来10/2 = 5是int类型 表示程序员指定要double类型的结果,编译报错后果我承担。编译器会把5=>5.0再赋值给rel

现在我们再来看自定义数据类型,也就是类。类也会发生自定类型转换,那么他的转换规则,根据书籍描述如下:

在C++中,将一个参数的构造函数用作自动类型转换函数,它是自动进行的,不需要显式的转换。

Person p(123); //正统的C++写法,创建一个对象 用() 表明正在调用构造函数对对象属性进行初始化赋值


Person p = Person(100); //显示转换,系统会先调用默认构造函数创建Person对象p,再把int参数构造函数后分配对象引用赋值给p

Person p;
p = Person(100); //等价写法

因为存在类中定义了自动转换函数,所以也允许开发者这样写。

Person p = 123; //会发生隐式转换 调用自动类型转换函数 将int类型 转换Person类型

Person p; 
p = 123; //同理,赋值运算符 会先将int类型 转换为Person类型后 再赋值给 p

示例:看看存在自动类型转换后,我们可以采用的构建对象策略

#include <iostream>
#include <vector>
#include <list>
using  namespace std;

class Person {

	friend ostream& operator<<(ostream& cout, const Person& p) {
		return cout << "Person(id=" << p.id << ",name=" << p.name << ")";
	}

private:
	int id;
	string name;
public:
	Person(){}
	Person(const int id) {
		this->id = id;
	}
	Person(const string& name) {
		this->name = name;
	}
};


void printInfo(const Person& p) {
	cout << p;
}


int main()
{
	Person p = 12;

	string name = "wangnaixing"; 
	Person p = name; //看着就不像创建对象,对成员变量初始化。但他确实初始化了~~~


	Person p;
	p = name;
	p = 12;

	printInfo(12); //这调用,这方法定义你不看你能知道形参是个类??
	printInfo(name); 
}

总结一下:

1)一个类可以有多个转换函数。

2)多个参数的构造函数,除第一个参数外,如果其它参数都有缺省值,也可以作为转换函数。意味着我们可以通过自动类型转换,让编译器自己去找到指定的构造函数。

3)隐式转换发生的场景:

将对象Person初始化为int值时 Person p = 12;

将int值通过赋值运算符赋给Person对象时 Person p; p = 12;

将int值传递给接受Person参数的函数时 printInfo(12);

函数返回值被声明为Person 试图返回int值时

explicit

将构造函数用作自动类型转换函数似乎是一项不错的特性,但有时候会导致意外的类型转换。explicit关键字用于关闭这种自动特性,在实际开发中,如果强调的是构造,建议使用explicit,如果强调的是类型转换,则不使用explicit。

我们来演示下,在Person(const int id) 构造方法中,加入 explicit 关键字。

	explicit Person(const int id) { //强调是构造不允许你用自动类型转换
		this->id = id;
	}

提示标错了。

image-20230326152503704

转换函数

构造函数只能用于某个内置类型转换为类类型,如果我们想逆向这个过程,将类类型 转换为内置类型改如何处理呢?

如果要进行相反的转换,可以使用特殊的运算符函数-转换函数。

语法:operator 数据类型();

注意:转换函数必须是类的成员函数;不能指定返回值类型;不能有参数。

#include <iostream>
#include <vector>
#include <list>
using  namespace std;

class Person {

	friend ostream& operator<<(ostream& cout, const Person& p) {
		return cout << "Person(id=" << p.id << ",name=" << p.name << ")";
	}

private:
	int id;
	string name;
public:
	Person(){}

	explicit Person(const int id) { //强调是构造不允许你用自动类型转换
		this->id = id;
	}
	Person(const string& name) {
		this->name = name;
	}
	void setName(const string& name) {
		this->name = name;
	}
	void setId(const int id) {
		this->id = id;
	}

	//转换函数在这里
	operator string() {
		return name;
	}
	operator int() {
		return id;
	}
	
};


int main()
{
	Person p;
	p.setId(1);
	p.setName("wangnaixing");

	cout << (int)p; //输出1
	cout << (string)p; //输出wangnaixing
    
    
    string demo = p;
	int demo2 = p;

	cout << demo; //隐式调用 真的炸
	cout << demo2; //如果真的有兄弟这么用,真的就自求多福了。
}

函数模板

函数模板是比方法重载,更简洁的实现代码复用的方式。这种情况下的重载,内部逻辑都是一样的,唯一的区别就是数据类型不同。

void swap(int& x1, int& x2) {
	int temp = x1;
	x1 = x2;
	x2 = temp; //看看重载的逻辑,为了适配不同数据类型,却需要定义三个不同的函数。这种便利只能在调用时被体现。
}


void swap(double& x1, double& x2) {
	double temp = x1;
	x1 = x2;
	x2 = temp;
}

void swap(long& x1, long& x2) {
	long temp = x1;
	x1 = x2;
	x2 = temp;
}

所以,我们可以考虑使用函数模板来优化下。

#include <iostream>         
using namespace std; 



template <typename T>
void Swap(T& x1, T& x2); //超级像Java的泛型


int main()
{

	int a = 3, b = 4;
	Swap(a, b);
	cout << a << b;
	
	double x = 3.3,y = 4.4;
	Swap(x, y);
	cout << x << y;
}


template <typename T>
void Swap(T& x1, T& x2) { //是不是用了模板之后,比重载在代码层面上更加整洁了?
	T temp = x1;
	x1 = x2;
	x2 = temp;
}

如果出现了函数模板的逻辑,不适用与某些类型的情况,比如我自己定义的Student类,我期望是交换Student类里面的成员age 这是时候我就可以定义函数模板的具体化

模板的具体化

具体化(特例化、特化)的语法:

template<> void 函数模板名<数据类型>(参数列表)
template<> void 函数模板名 (参数列表)
{
	// 函数体。
}

#include <iostream>         
using namespace std; 



template <typename T>
void Swap(T& x1, T& x2);


class  Student
{
	friend ostream& operator <<(ostream& cout, const Student& stu) {
		return cout << "Student(id=" << stu.id << ",name=" << stu.name << ",age=" << stu.age << ")";
	}



private:
	int id;
	string name;
	int age;
public:
	Student(const int id, const string name, const int age) {
		this->id = id;
		this->name = name;
		this->age = age;
	}

	int getAge() {
		return age;
	}
	void setAge(const int age) {
		this->age = age;
	}

};


template<typename T>
void Swap(T& a, T& b);


template<>  //Studnet 是Swap模板的一个需要特情处理的情况
void Swap<Student>(Student& s1, Student& s2);




int main()
{
	Student s1{ 1,"wangnaixing",20 };
	Student s2{ 2,"zhangsan",19 };
	Swap(s1, s2);
	cout << s1;
	cout << s2;


	
}


template <typename T>
void Swap(T& x1, T& x2) {
	T temp = x1;
	x1 = x2;
	x2 = temp;
}


template<>
void Swap<Student>(Student& s1, Student& s2) {
	int temp = s1.getAge(); //写上我们的处理逻辑。仅仅交换年龄。
	s1.setAge(s2.getAge());
	s2.setAge(temp);
}

函数声明回顾

回顾一下,到目前为止C++函数一共有多少种声明情况:

  • 1、无形参无返回值
void helloWorld() {
	cout << "Hello World C++";
}
  • 2、有形参无返回值
void bulleSort(int arr[], int len) {
	int flag = true;
	for (int i = 0; i < len-1 && flag; i++)
	{
		flag = false;
		for (int j = len-1; j >=i; j--)
		{
			if (arr[i] > arr[j]) {
				swap(arr[i], arr[j]);
				flag = true;
			}
		}
	}
}
  • 3、可变形参
/// <summary>
/// 获取可变参数
/// </summary>
/// <param name="num">可变参数总和</param>
/// <param name="...">逗号分隔</param>
void getAverage(int num, ...) {
    va_list vl;
    va_start(vl, num); //num很重要,他确定栈元素个数
    
    //由栈底往栈顶的过程..
    while (num != 0) {
        char* value = va_arg(vl, char*);
        cout << value;
        num--;
    }
    
    va_end(vl); //让指针指向空。不然后期可能会变成一个野指针。出现新的问题。


}
  • 4、指针作为形参的
void getSeconds(unsigned int *par){
     *par = time(NULL);
    return;
}
  • 5、指针作为函数返回值
int *getRandom() {
    // C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
    static int r[10];
    srand((unsigned) time(NULL));
    for (int i = 0; i < 10; ++i) {
        r[i] = rand();
        printf("%d\n", r[i] );
    }
    return r;
}
  • 6、一维数组作为形参
#include <iostream>
using namespace std;

//void forEach(int arr[],int length)
void forEach(int* arr,int length) {
	for (size_t i = 0; i < length; i++)
	{
		cout << "a[" << i << "]=" << arr[i]<<endl;
	}
}

int main() {
    int arr[] = { 1, 2, 3 };
	forEach(arr,sizeof(arr)/sizeof(int));

}
  • 7、二维数组作为形参
#include <iostream>
using namespace std;


void forEach(int p[][3],int len){ //二维数组名等价于行地址
//void forEach(int(*p)[3], int len) {
	for (int i = 0; i < len; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			cout << " p[" << i << "][" << j << "] = " << p[i][j];
		}
		cout << endl;

	}

}

int main() {
	int arr[][3] = { {11,12,13},{21,22,23} };
	
	forEach(arr, 2);
}
  • 8、三维数组作为形参
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>       
using namespace std;


void func(int(*p)[2][3], int len) {
	int number = 1;
	for (int a = 0; a < len; a++)
	{
		for (int b = 0; b < 2; b++)
		{
			for (int c = 0; c < 3; c++)
			{
				p[a][b][c] = number++;
			}

		}

	}

}

void forEach(int p[][2][3], int len) {
	for (int a = 0; a < len; a++)
	{
		for (int b = 0; b < 2; b++)
		{
			for (int c = 0; c < 3; c++)
			{
				cout << p[a][b][c] << "\t";
			}
			cout << endl;

		}

	}

	cout << endl << endl;

}

int main()
{
	int bh[4][2][3];
	memset(bh, 0, sizeof(bh));

	func(bh, 4);

	forEach(bh, 4);
}
  • 9、结构体作为形参
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

struct  Student
{
    int id;
    char name[20];


};

void printBook(struct Student stu) {
    cout << "Student(id=" << stu.id << ",name=" << stu.name << ")";
}


int main() {
    Student stu{ 1,"wangnaixing"};
    printBook(stu);  

}

  • 10、结构体指针作为形参
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

struct  Student
{
    int id;
    char name[20];


};

void modifyName(Student* stu) {
    strcpy(stu->name, "zhangsanhahaha");
}

int main() {
    Student stu{ 1,"wangnaixing"};
    modifyName(&stu);
}
  • 11、引用作为形参
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class Student {
private:
    int id;
    string name;
public:
    Student(const int id, const string name) :id(id), name(name) {};
    void setName(const string name) {
        this->name = name;
    }
    void setId(const int id) {
        this->id = id;
    }
    void show() {
        cout << "id=" << id << ",name" << name << endl;
    }
};

void modifyStudent(Student& stu) { //感觉有规律,反正是类就用引用,是结构体就用指针
    stu.setId(999);
    stu.setName("遭老罪了~~~");
    
}

int main() {
    Student stu{ 1,"wangnaixing"};
    modifyStudent(stu);
    stu.show();
}
  • 12、结构体引用作为形参
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>       
using namespace std;

struct Student {
	int id;
	string name;
};

void printStudentInfo(Student& student) {
	student.id = 666;
	student.name = "modifyName";
	cout << "我是第" << student.id << "号的学生,我的名字叫" << student.name;
}


int main()
{
	
	Student student = { 1,"wangnaixing" };
	printStudentInfo(student);

}
  • 13、引用作为返回值的
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>       
using namespace std;

struct Student {
	int id;
	string name;
};

Student& func(Student& student) {
	student.id = 666;
	student.name = "modifyName";

	return student;
}


int main()
{
	
	Student student = { 1,"wangnaixing" };
	Student modifyStudent = func(student);
	cout << "我是第" << modifyStudent.id << "号的学生,我的名字叫" << modifyStudent.name;

}

  • 14、函数后置处理

将返回类型移到了函数声明的后面。
auto是一个占位符(C++11给auto新增的角色), 为函数返回值占了一个位置。

int func(int x,double y);
等同:
auto func(int x,double y) -> int;

这种语法也可以用于函数定义:

auto func(int x,double y) -> int
{
    // 函数体。
}

没啥意义,可能是决定返回值在前面太丑了??像JS一样弄个箭头函数去掉function?额额额,auto 这里只是一个占位符。。。人家JS用箭头函数好歹还处理了 this指针的问题呢。

猜你喜欢

转载自blog.csdn.net/WNX10086/article/details/129786274