十一、类型转换
1.隐式转换
//1
char c = 'a';
int i = c; //隐式转换
//2
int func(void)
{
char c = 'a';
return c; //隐式转换
}
//3
void func(int i){}
char c = 'a';
func(c); //隐式转换
2.显示转换
1)C++兼容C中强制类型转换
char c = 'a';
int i = (int)c;//C风格的强转
int i = int(c);//C++风格的强转
2)C++扩展了四种操作符形式的类型转换
①静态类型转换
语法:
目标类型变量=static_cast<目标类型>(源类型变量)
使用场景:
用于将void*转换为其它类型的指针。
#include <iostream>
using namespace std;
int main(void)
{
int* pi = NULL;
//int num = (int)pi;//C风格
//int num = int(pi);//C++风格
//int num = static_cast<int>(pi);//error
void* pv = pi;
pi = static_cast<int*>(pv);//合理,ok
return 0;
}
②动态类型转换
语法:
目标类型变量=dynamic_cast<目标类型>(源类型变量);
③常类型转换
语法:
目标类型变量=const_cast<目标类型>(源类型变量)
使用场景:
用于去除指针或引用的常属性
#include <iostream>
using namespace std;
int main(void)
{
/* volatile修饰变量表示易变的,告诉编译器
* 每次在使用该变量时都要小心从内存中重
* 新读取,不要直接使用寄存器中的副本,防
* 止编译器优化导致的错误结果*/
volatile const int ci = 100;
int* pci = const_cast<int*>(&ci);
*pci = 200;
cout << "ci=" << ci << endl;//200
cout << "*pci=" << *pci << endl;//200
cout << "&ci=" << (void*)&ci << endl;
cout << "pci=" << pci << endl;
return 0;
}
④重解释类型转换
语法:
目标类型变量=reinterpret_cast<目标类型>(源类型变量)
使用场景:
--》指针和整数之间进行显式转换
--》在任意类型的指针或引用之间显式转换
eg:向物理地址0x12345678存放一个数据100
int* paddr=reinterpret_cast<int*>(0x12345678);
*paddr = 100;
#include <iostream>
using namespace std;
int main(void)
{
//"\000"-->'\0'-->0
char buf[]="0001\00012345678\000123456";
struct Net{
char type[5];
char id[9];
char passwd[7];
};
Net* pn = reinterpret_cast<Net*>(buf);
cout << pn->type << endl;//0001
cout << pn->id << endl;//12345678
cout << pn->passwd << endl;//123456
return 0;
}
小结:来自C++社区给C程序的建议
1)慎用宏,可以使用const、enum、inline替换
#define PAI 3.14
==> const double PAI = 3.14;
#define SLEEP 0
#define RUN 1
#define STOP 2
==> enum STATE{SLEEP,RUN,STOP};
#define max(a,b) ((a)>(b)?(a):(b))
==> inline void max(int a,int b){
return a>b?a:b;
}
2)变量随用随声明同时初始化
3)尽量使用new/delete替换malloc/free
4)少用void*、指针计算、联合体、强制转换
5)尽量使用string表示字符串,少用C风格char*/char[]
十二、类和对象
1 什么是对象
万物皆对象,任何一种事物都可以看做是对象。
2 如果描述对象
通过对象的属性(名词、形容词、数量词)和行为(动词)来描述。
3 面向对象的程序设计
对自然世界中对象的描述引入到编程实践中的一种理念和方法,这种方法称为"数据抽象",即描述对象时把对象细节的东西剥离出去,只考虑一般性的、有规律性的、统一的东西。
4 什么是类
类是多个对象共性提取出来定义的一种新的数据类型,是对 对象属性和行为的的抽象描述.
现实世界 类 编程世界
具体对象--抽象-->属性/行为--实例化-->具体对象
十三 类的定义和实例化
1 类的一般语法形式
class/struct 类名:继承方式 基类{
访问控制限定符:
类名(形参表):初始化列表{...}//构造函数
~类名(void){...}//析构函数
返回类型 函数名(形参表){...}//成员函数
数据类型 变量名;//成员变量;
};
2 访问控制限定符
1)public:公有成员,任何位置都可以访问
2)private:私有成员,只有类内部中的成员函数才能访问
3)protected:保护成员(后面讲)
注:使用class定义的类,类中成员的默认访问控制属性是private;如果使用struct定义的类,类中成员默认的访问控制属性是public.
eg:
class/struct XX{
int a;//私有/公有
private:
int b;//私有成员
public:
int c;//公有成员
int d;//公有成员
private:
int e;//私有成员
};
3.构造函数(constructor):主要完成对象的初始化
1)函数名和类型相同,没有返回值
2)构造函数在创建对象时自动调用,不能显示调用
#include <iostream>
using namespace std;
class Student{
public:
Student(const string& name,int age,
int no){
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_no = no;
}
void who(void){
cout << "我叫" << m_name << ",今年"
<< m_age << "岁,学号是" << m_no
<< endl;
}
private:
string m_name;
int m_age;
int m_no;
};
int main(void)
{
//创建对象,(...):指明构造函数需要的实参
Student s("张飞",25,10086);
s.who();
//构造函数不能像普通成员函数一样显式调用
//s.Student("张三",26,10011);
return 0;
}
4.对象的创建和销毁
1)在栈区创建单个对象
类名 对象(构造实参表);
类型 对象 = 类名(构造实参表);
2)在栈区创建多个对象
类名 对象数组[元素个数] = {类名(构造实参表),...};
3)在堆区创建/销毁单个对象
创建:
类名* 对象指针 = new 类名(构造实参表);
注:new操作符除了会分配内存还会自动表用构造函数,而malloc只会分配内存,不会调用构造函数。
销毁:
delete 对象指针
4)在堆区创建/销毁多个对象
创建:
类名* 对象指针 = new 类名[元素个数]{类名(构造实参表),...};
销毁:
delete[] 对象指针
参考代码:
#include <iostream>
using namespace std;
class Student{
public:
Student(const string& name,int age,
int no){
cout << "构造函数" << endl;
m_name = name;
m_age = age;
m_no = no;
}
void who(void){
cout << "我叫" << m_name << ",今年"
<< m_age << "岁,学号是" << m_no
<< endl;
}
private:
string m_name;
int m_age;
int m_no;
};
int main(void)
{
//创建对象,(...):指明构造函数需要的实参
//Student s("张飞",25,10086);
Student s = Student("张飞",25,10086);
s.who();
//栈中创建对象数组
Student sarr[3] = {
Student("貂蝉",26,10087),
Student("小乔",22,10088),
Student("大乔",27,10089)};
sarr[0].who();
sarr[1].who();
sarr[2].who();
//堆区创建单个对象
Student* ps =
new Student("孙尚香",28,10011);
ps->who();//(*ps).who();
delete ps;
ps = NULL;
//堆区创建多个对象,C++11支持
Student* parr = new Student[3]{
Student("林黛玉",28,10012),
Student("潘金莲",29,10013),
Student("孙二娘",35,10014)};
parr[0].who();//(*(parr+0)).who()
parr[1].who();
parr[2].who();
delete[] parr;
parr = NULL;
return 0;
}
5.多文件编程
1)类的声明一般放在头文件中
#ifndef __STUDENT_H
#define __STUDENT_H
#include <iostream>
using namespace std;
//类的声明
class Student{
public:
Student(const string& name,int age);
void who(void);
private:
string m_name;
int m_age;
};
#endif//__STUDENT_H
2)类的定义放在源文件中
#include "Student.h"
//类的定义,需要在成员函数名字前面加"类名::"
Student::Student(const string& name,int age){
cout << "构造函数" << endl;
m_name = name;
m_age = age;
}
void Student::who(){
cout << m_name << ',' << m_age << endl;
}
十四、构造函数和初始化表
1.构造函数可以重载、也可以带有缺省参数
2.缺省构造函数(无参构造)
1)如果类中没有定义任何构造函数,编译器会提供一个缺省构造函数:
①对于基本类型的成员变量不做初始化
②对于类类型的成员变量(成员子对象),会自动调用相应的无参构造函数来初始化
class A{
int m_i
string m_s;
};
A a;
cout << a.m_i << endl;//未初始化的结果
cout << a.m_s << endl;//一定是空字符串
2)如果定义了构造函数,无论是否有参数,那么编译器都不会提供缺省构造函数,构造函数的参数必须与形参完全对应,不然编译不通过
3.类型转换构造函数(单参构造函数)
class 目标类型{
[explicit] 目标类型(源类型){...}
};
注:可以实现源类型到目标类型的隐式转换,使用explicit关键字,可以强制要求这种类型转换必须要显示完成
#include <iostream>
using namespace std;
class Integer{
public:
Integer(void){
cout << "Integer(void)" << endl;
m_i = 0;
}
//类型转换构造函数
/*explicit*/ Integer(int i){
cout << "Integer(int)" << endl;
m_i = i;
}
void print(void){
cout << m_i << endl;
}
private:
int m_i;
};
int main(void)
{
Integer i1;
i1.print();//0
//首先将20隐式转换为Integer对象
//再使用转换之后的临时对象给i1赋值
i1 = 20;
i1.print();//20
//隐式转换代码可读性差,推荐显式转换
//i1 = (Integer)30;//C风格
i1 = Integer(30);//C++风格
i1.print();//30
return 0;
}
4.拷贝构造函数
1)用已存在的对象构造同类型的副本对象,会调用该类型的拷贝构造函数;(拷贝构造函数也有初始化表)
class 类名{
类名 (const 类名& ):初始化表
{......}
};
参考代码:
class A{
public:
A(int data=0){
m_data=data;
}
A(const A& that){
cout << "拷贝构造函数" << endl;
m_data=that.m_data;
}
private:
int m_data;
};
int main()
{
A a1(100);
A a2(a1);
return 0;
}
2)如果类中自己没有定义拷贝构造函数,那么编译器会为该类提供一个缺省的靠背构造函数:
①对于基本类型的成员变量,按字节复制
②对于类类型的成员变量(成员子对象),将自动调用相应类的拷贝构造函数来初始化
注:一般不需要自己写拷贝构造函数,因为编译器缺省提供的已经很好用了
3)靠背构造函数调用时机
①用已经定义的对象作为同类型对象的构造实参
②以对象形式向函数传递参数
③从函数返回对象