13 C++类型转换
C风格转换【(TYPE)EXPRESSION】可在任意类型之间转换,且不易查找。所以C++引进了四种类型转换操作符,解决以上问题。
类型 | 主要用途 |
static_cast | 静态类型转换 |
dynamic_cast | 子类和父类间多态类型转换 |
const_cast | 去掉const属性转换 |
reinterpreter_cast | 重新解释类型转换 |
13.1 static_cast
static_cast<目标类型>(标识符)
静态,即在编译期内就可完成其类型转换,最经常使用
#include<iostream> int main(){ double pi = 3.1415926; int num1 = (int)pi; // C语言旧式类型转换 int num2 = pi; // 隐式类型转换 //C++静态类型转换 //在编译期完成基本类型的转换,且有类型检查 int num3 = static_cast<int> (pi); std::cout << "num1 = " << num1 << std::endl; std::cout << "num2 = " << num2 << std::endl; std::cout << "num3 = " << num3 << std::endl; system("pause"); return 0; }
13.2 dynamic_cast
dynamic_cast<目标类型>(标识符)
用于多态中父子类之间的多态转换,转换类指针时,基类需要虚函数
#include<iostream> class Animal{ public: virtual void shout() = 0; }; class Dog : public Animal { public: virtual void shout(){ std::cout << "汪汪" << std::endl; } void dohome() { std::cout << "看家" << std::endl; } }; class Cat : public Animal { public: virtual void shout(){ std::cout << "喵喵" << std::endl; } void dohome() { std::cout << "抓老鼠" << std::endl; } }; int main(){ Animal* base = NULL; base = new Cat(); base->shout(); //将父类指针转成子类 //此时转换失败,因为父类指针现在指向的对象是猫 //转换失败返回空(NULL) Dog* dog = dynamic_cast<Dog*> (base); if (NULL!=dog) { dog->shout(); dog->dohome(); } //此时转换成功,成功将父类指针转换成子类指针 Cat* cat = dynamic_cast<Cat*> (base); if (cat != NULL){ cat->shout(); cat->dohome(); } system("pause"); return 0; }
13.3 const_cast
const_cast<目标类型>(标识符):目标类型只能是指针或者引用
#include<iostream> class A { public: int data; }; int main{ //类似于结构体的初始化方式 const A a = { 200 }; //编译失败:const_cast 目标类型只能是引用或者指针 //A a1 = const_cast<A> (a); A& a2 = const_cast<A&> (a); a2.data = 100; std::cout << &a << "——" << a.data << std::endl; std::cout << &a2 << "——" << a2.data << std::endl; A* a3 = const_cast<A*> (&a2); a3->data = 100; std::cout << &a3 << "——" << a3->data << std::endl; system("pause"); return 0; }
扩展:取址符号的解释
#include<iostream> //& 表示取址符号,取的是当前变量本身的内存地址 int a = 1; // 变量a int &c = a; // 引用c int *b = &a; // 指针b int main() { std::cout << &c << std::endl; //输出的是c的存储地址也就是a的地址 std::cout << *&c << std::endl; //*取这个地址的值,输出1 std::cout << b << std::endl; //输出指针指向的地址,即a的地址 std::cout << &b << std::endl; //取的是指针b本身的地址 system("pause"); return 0; }
13.4 reinterpret_cast
reinterpret_cast<目标类型>(标识符)
数据的二进制重新解释,但是不改变其值
reinterpret_cast是四种强制转换中功能最强大的,它可以暴力完成两个完全无关类型的指针之间或指针和数之间的互转,比如用char类型指针指向double值。它对原始对象的位模式提供较低层次上的重新解释(即reinterpret),完全复制二进制比特位到目标对象,转换后的值与原始对象无关但比特位一致,前后无精度损失
指针和数之间的互转
#include<iostream> int main(){ double d = 12.1; char* p = reinterpret_cast<char*>(&d); std::cout << "*p的值:" << *p << std::endl; // 将d以二进制(位模式)方式解释为char,并赋给*p double* q = reinterpret_cast<double*>(p); std::cout << "*q的值:" << *q << std::endl; // 12.1 }
无关类型指针之间的类型转换
#include<iostream> class Person{ public: void run(){ std::cout << "我在不停的奔跑" << std::endl; } }; class Sky{ public: void laugh(){ std::cout << "天空乌云密布,暴雨要来了" << std::endl; } }; int main(){ Person* p = new Person(); p->run(); Sky* sky = reinterpret_cast<Sky*> (p); sky->laugh(); std::cout << &p << "——" << std::endl; std::cout << &sky << "——" << std::endl; system("pause"); return 0; }
14 C++内存知识
14.1 内存区域划分
在C++中,程序在内存中的存储被分为五个区:
1、代码区(Text):只读,共享,静态分配,存放程序执行代码,也可能包含常量
2、全局区:编译时分配内存,程序结束后由系统释放,存放全局变量和静态变量
DATA: 也叫GVAR(global value),存放已初始化的非零全局变量
BSS:存放零值或未初始化的全局变量,在程序开始时被清零
3、栈区(stack):由编译器自动分配释放 ,存放函数参数值,局部变量值等,按内存地址由高到低方向生长,其空间大小由编译时确定,速度快,空间小
4、堆区(heap):由程序员分配释放,按内存地址由低到高方向生长,其空间大小由系统内存上限决定,速度慢,空间大
每个线程都会有自己的栈,但是堆空间是共用的,下图为C++内存区域划分图
14.2 列举分配空间场景
区域类型 | 列举场景 |
代码区 | 类的成员函数和全局函数 |
全局区 | 全局变量和静态变量 |
栈区 | 局部变量和函数参数值 |
堆区 | 调用malloc函数或new运算符重载申请空间 |
14.3 变量的生命周期
类型 | 内存划分 | 作用范围 | 生命周期 |
局部变量 | 栈 | 定义局部变量所在的大括号内 | 定义的函数被调用时,在栈开辟空间,函数调用结束,被清除 |
全局变量 | 全局区 | 所有源文件 | 在编译期即分配好了内存空间,在程序运行结束时,被清除 |
静态局部变量 | 全局区 | 定义局部变量所在的大括号内 | 在编译期即分配好了内存空间,在程序运行结束时,被清除 |
静态全局变量 | 全局区 | 只能在定义的文件中调用 | 在编译期即分配好了内存空间,在程序运行结束时,被清除 |
14.4 名称空间的用途
避免变量或函数重命名,命名空间可以将多个变量和函数等包含在内,使其不会与命名空间外的任何变量和函数等发生重命名的冲突
namespace ride{ void PrintNum(int x, int y){ std::cout << x*y << std::endl; } }//打印两数相乘的命名空间 namespace plus{ void PrintNum(int x, int y){ std::cout << x+y << std::endl; } }//打印两数相加的命名空间 int main() { ride::PrintNum(1, 4); plus::PrintNum(1, 4); system("pause"); return 0; }
14.5 运行Test函数
例一
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
结果:编译成功,运行失败
原因:因为调用GetMemory相当于值传递,str指针实际上并没有分配到内存空间,访问空指针会运行失败,因为在C++中内存编号0 ~255为系统占用内存,内存地址编号为0的空间不允许用户访问
详细过程:在函数地址入栈后,p作为形参,只是拷贝了一份str的地址,并为该地址申请了空间,但是str指向的地址仍然是NULL
解决方法一
void GetMemory(char **p) { *p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); }
解决方法二
void GetMemory(char* &p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
例二
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
结果:打印结果不确定
原因:调用函数返回值是局部变量的指针,局部变量在函数被调用结束后,在栈区的内存空间被释放。因此str在打印时,指针指向的是非法(不可控)的内存地址,即野指针,在C++中访问野指针的结果是不确定的
例三
void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }
结果:正常打印hello
原因:地址传递,把str指针的地址当做实参传递给形参,并在GetMemory2中为该地址申请地址空间
例四
void Test(void) { char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL) { strcpy(str, “world”); printf(str); } }
结果:运行时中断,后打印world
原因:free只是释放了malloc所申请的内存,并没有改变指针的值,因此由于指针所指向的内存已经被释放,所以其它代码有机会改写其内容,相当于该指针指向了自己无法控制的地方,也称为野指针。为了避免操作野指针,在free后应将指针指向NULL
15 函数的使用
15.1 函数指针
函数指针:指向函数的指针,可通过指针访问函数,函数名作为实参使用时
作用:通过使用函数指针可以简化代码,在一定程度上提高代码可读性
#include<iostream> int add(int a, int b){ return a + b; } typedef int(*FucPoint)(int, int); //使用函数指针作为形参 void myFun(int a, int b, FucPoint p){ p(a, b); } int main(){ //函数名作为入参时,C++会自动将其转换为函数指针 myFun(2, 3, add); system("pause"); return 0; }
15.2 静态函数
静态函数:被static关键字修饰的函数,静态成员函数属于类,不创建对象也可调用。类成员函数与静态成员函数的区别在于普通成员函数有一个隐藏的调用参数(this)指针
应用场景:从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象,反之使用静态方法
15.3 编程实现冒泡
要求:
创建一个无序的数组,通过定义的排序算法函数sort排序并输出
排序算法函数sort使用排序规则回调函数(函数指针)作为参数传入
#include<iostream> bool judge(int a, int b){ return a>b?true:false; } typedef bool(*FucPoint)(int, int); class SortUtil{ public: static int* sort(int arr[], int len, FucPoint p){ for (int i = 0; i < len - 1; ++i) { for (int j = 0; j < len - 1 - i; ++j) { if (p(arr[j] , arr[j + 1])) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; } }; int main() { int arr[9] = { 4, 2, 8, 0, 5, 7, 1, 3, 9 }; int len = sizeof(arr) / sizeof(int); SortUtil::sort(arr,len,judge); system("pause"); return 0; }