一: 普通类型的类型转换:

当较低类型的数据转换为较高类型时,一般只是形式上有所改变, 而不影响数据的实质内容,(自动)
而较高类型的数据转换为较低类型时则可能有些数据丢失。(强制)

如int a=(int)3.14f;
a=3;
就是较高类型转换为较低类型,数据丢失。

总结:

自动类型转换:
较低类型到较高类型转换:
从低到高: char ->int ->unsigned->long-> double
强制类型转换:可能精度丢失,高类型-》低类型
赋值中的转换。

1.自动类型转换:(较低规则转换为较高规则)

较低类型转换为较高类型,然后再参加运算
double ←── float 高

long

unsigned

int ←── char,short 低

2 .赋值中的类型转换 :把赋值运算符右侧表达式的类型转换为左侧变量的类型

具体的转换如下:
(1) 浮点型与整型 
● 将浮点数(单双精度)转换为整数时,将舍弃浮点数的小数部分, 只保留整数部分。
将整型值赋给浮点型变量,数值不变,只将形式改为浮点形式, 即小数点后带若干个0。
注意:赋值时的类型转换实际上是强制的。
(2) 单、双精度浮点型 
● 由于C语言中的浮点值总是用双精度表示的,所以float 型数据
只是在尾部加0延长为doub1e型数据参加运算,然后直接赋值。
doub1e型数据转换为float型时,通过截尾数来实现,截断前要进行四舍五入操作。

(3) char型与int型

● int型数值赋给char型变量时,只保留其最低8位,高位部分舍弃。 
● chr型数值赋给int型变量时, 一些编译程序不管其值大小都作正数处理,

而另一些编译程序在转换时,若char型数据值大于127,就作为负数处理。
对于使用者来讲,如果原来char型数据取正值,转换后仍为正值;
如果原来char型值可正可负,则转换后也仍然保持原值, 只是数据的内部表示形式有所不同。

(4) int型与1ong型 
● long型数据赋给int型变量时,将低16位值送给int型变量,
而将高16 位截断舍弃。(这里假定int型占两个字节)。 
将int型数据送给long型变量时,其外部值保持不变,而内部形式有所改变。 
(5) 无符号整数 
● 将一个unsigned型数据赋给一个占据同样长度存储单元的整型变量时
(如:unsigned int→int、unsigned long→long,unsigned short→short) 
原值照赋,内部的存储方式不变,但外部值却可能改变。
● 将一个非unsigned整型数据赋给长度相同的unsigned型变量时, 
内部存储形式不变,但外部表示时总是无符号的。 
/*例:赋值运算符举例 */
main()
{ unsigned a,b;
  int i,j;
  a=65535;
  i=-1;
  j=a;
  b=i;
  printf("(unsigned)%u→(int)%d\n",a,j);
  printf("(int)%d→(unsigned)%u\n",i,b);
}
运行结果为:
(unsigned)65535→(int)-1
(int)-1→(unsigned)65535

● 计算机中数据用补码表示,int型量最高位是符号位,为1时表示负值,为0时表示正值。
如果一个无符号数的值小于32768则最高位为0,赋给 int型变量后、得到正值。
如果无符号数大于等于32768,则最高位为1, 赋给整型变量后就得到一个负整数值。
反之,当一个负整数赋给unsigned 型变量时,得到的无符号值是一个大于32768的值。 
● C语言这种赋值时的类型转换形式可能会使人感到不精密和不严格,因为不管表达式的值怎样,
系统都自动将其转为赋值运算符左部变量的类型。 
● 而转变后数据可能有所不同,在不加注意时就可能带来错误。 
这确实是个缺点,也遭到许多人们批评。但不应忘记的是:
c面言最初是为了替代汇编语言而设计的,所以类型变换比较随意。
当然, 用强制类型转换是一个好习惯,这样,至少从程序上可以看出想干什么。

二: 子类与父类之间的类型转换:

1.结论:子类可以到父类自动转换,父类到子类需要强制转换、
因为子类中可能定义了新的变量/函数。

结论: 1、有virtual才可能发生多态现象

2、不发生多态(无virtual)调用就按原类型调用,即指针自身的类型。

3. 父类指针指向子类的对象,如果父类可调用子类中重写的virtual 函数,
  调用的其余函数都是父类中的函数。
  
   4. 子类的指针指向父类的对象(强制转换),子类指针可调用父类中的虚函数,
其余调用的都是子类中的函数。
 
5. 多态是: 父类指针指向子类的对象,然后调用子类中的虚函数。

2.如果一个对象与另一个对象没有任何的继承关系,那么他们就不能进行类型转换。
如果要把一个派生类对象赋值给基类对象这个称为上溯造型。
如果要把基类对象赋值给派生类对象就需要强制类型转换,这称为下溯造型,下溯造型有一些危险,
要安全的进行下溯造型有一个前题,基类对象必须是从派生类对象中上溯过来的。

3.范例:

子类转换成父类
class A {
public void func1() {
 System.out.println("A func1 is calling.");
}
public void func2() {
 func1();
}
}
class B extend A {
public void func1() {
 System.out.println("B func1 is calling.");         
}
public void func3() {
 System.out.println("B func3 is calling.");
}
}
class C {
public static void main(String[] args) {
 B b = new B();
 A a = b;
 callA(a);
 callA(new B());
}
public void callA(A a) {
 a.func1();
 a.func2();
}
}
编译器能够自动将类B的实例对象b直接赋值给A类的引用变量,也就是子类能够自动转换成父类类型。
另外,程序可以直接创建一个类B的实例对象,传递给需要类A的实例对象作参数的callA()方法,
在参数传递的过程中发生了隐式自动类型转换。子类能够自动转换成父类的道理非常容易理解。

父类转换成子类:
如果知道callA方法中传递的参数a实际上就是子类B的一个引用对象,想在callA方法中调用子类的特有方法,如何做:
public void callA(A a) {
 a.func1();
 a.func2();
 a.func3();
}
编译有问题,因为对编译器来说,它只分析程序语法,它只知道变量a的引用类型是类A,
而类A又没有func3这个方法,所以编译不通过。代码改为:
public void callA(A a) {
 B b = a;
 a.func1();
 a.func2();
 a.func3();
}
编译还是有问题,因为编译器是不能将父类对象自动转换成子类的。
public void callA(A a) {
 B b = (B)a;     //强制类型转换
 a.func1();
 a.func2();
 a.func3();
}
 
-instanceof 操作符-
可以用instanceof判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类
public void callA(A a) {
if(a instanceof B){
 B b = (B)a;
 a.func1();
 a.func2();
 a.func3();
} else {
 a.func1();
 a.func2();
}
}

4. 多态(或说  一个接口(基类,虚函数),多种方法)

(父类的指针指向子类的对象,然后用父类指针调用子类的重写虚函数)

(注意:如果调用的不是子类的重写的虚函数,而是一般的 重写函数,
则调用的还是父类中的函数 )

C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。
(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)

多态的作用:
封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。
而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,
函数都能够通过同一个接口调用到适应各自对象的实现方法。

多态的实现:

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,
可以根据指向的子类的不同而实现不同的方法。
如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,
将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。

在面向对象语言中,接口的多种不同实现方式即为多态。多态是指,用父类的指针指向子类的实例(对象),
然后通过父类的指针调用实际子类的成员虚函数。

多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的。

多态可以让父类的指针有“多种形态”,这是一种泛型技术。
(所谓泛型技术,就是试图使用不变的代码来实现可变的算法)。

范例:
#include<iostream>
using namespace std;

class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
return 0;
}
解析::

第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,
调用的都是基类本身的函数,因此输出结果就是1、2。
   
第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,
正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,
因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。
而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,
此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,
因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,
因此输出的结果也会是子类的结果4。
笔试的题目中还有一个另类测试方法。即
B *ptr = (B *)&a;  ptr->foo();  ptr->fun();
问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。
结果,这两句调用的输出结果是3,2。
并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,
但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,
于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。
而ptr->fun()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,
通过虚函数列表的引用,找到了基类中fun()函数的地址,因此调用了基类的函数。
由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。

结论: 1、有virtual才可能发生多态现象

2、不发生多态(无virtual)调用就按原类型调用,即指针自身的类型。

3. 父类指针指向子类的对象,如果父类可调用子类中重写的virtual 函数,
  调用的其余函数都是父类中的函数。
  
   4. 子类的指针指向父类的对象(强制转换),子类指针可调用父类中的虚函数,
其余调用的都是子类中的函数。
 
5. 多态是: 父类指针指向子类的对象,然后调用子类中的虚函数。

5.虚函数以及基类与接口的区别:
(详细见下面)

. 虚函数

2.1虚函数定义

在基类的类定义中,定义虚函数的一般形式:

Virtual 函数返回值类型 虚函数名(形参表)
{函数体}

虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public。

一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,
而不需要再显式地声明为virtual。

2.2 虚函数的作用

虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数进行重新定义(形式同上)。在派生类中定义的函数应与虚函数具有相同的形参个数和形参类型(覆盖),以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。

虚函数可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时,基类虚成员函数调用基类指针,则会调用其真正指向的对象的成员函数,而不是基类中定义的成员函数(只要派生类改写了该成员函数)。若不是虚函数,则不管基类指针指向哪个派生类对象,调用时都会调用基类中定义的那个函数。

2.3 实现动态联编需要三个条件:

1)必须把需要动态联编的行为定义为类的公共属性的虚函数;
2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来;
3)必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。

2.4 定义虚函数的限制

1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。

2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。

3)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、参数类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种非虚的同名同返回值同参数个数同参数类型函数。

2.5

1)为什么类的静态成员函数不能为虚函数:

如果定义为虚函数,那么它就是动态绑定的,也就是在派生类中可以被覆盖的,这与静态成员函数的定义(在内存中只有一份拷贝,通过类名或对象引用访问静态成员)本身就是相矛盾的。

2)为什么构造函数不能为虚函数:

因为如果构造函数为虚函数的话,它将在执行期间被构造,而执行期则需要对象已经建立,构造函数所完成的工作就是为了建立合适的对象,因此在没有构建好的对象上不可能执行多态(虚函数的目的就在于实现多态性)的工作。在继承体系中,构造的顺序就是从基类到派生类,其目的就在于确保对象能够成功地构建。构造函数同时承担着虚函数表的建立,如果它本身都是虚函数的话,如何确保vtbl的构建成功呢?

3)虚析构函数

C++开发的时候,用来做基类的类的析构函数一般都是虚函数。当基类中有虚函数的时候,析构函数也要定义为虚析构函数。如果不定义虚析构函数,当删除一个指向派生类对象的指针时,会调用基类的析构函数,派生类的析构函数未被调用,造成内存泄露。
虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这样,当删除指向派生类的指针时,就会首先调用派生类的析构函数,不会有内存泄露的问题了。
一般情况下,如果类中没有虚函数,就不用去声明虚析构函数。当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

2.6虚函数的实现——虚函数表

虚函数是通过一张虚函数表来实现的,简称V-Table。类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享,类的每个虚函数成员占据虚函数表中的一行。
在这个表中,主要是一个类的虚函数的地址表。这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。在有虚函数的类的实例中,分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就指明了实际所应该调用的函数。

3. 纯虚函数

许多情况下,在基类中不能对虚函数给出有意义的实现,则把它声明为纯虚函数,它的实现留给该基类的派生类去做。

纯虚函数的声明格式:virtual <函数返回类型说明符> <函数名> ( <参数表> )=0;

纯虚函数的作用是为派生类提供一个一致的接口。

4.抽象类(abstract class)

抽象类是指含有纯虚函数的类(至少有一个纯虚函数),该类不能创建对象(抽象类不能实例化),但是可以声明指针和引用,用于基础类的接口声明和运行时的多态。

抽象类中,既可以有抽象方法,也可以有具体方法或者叫非抽象方法。抽象类中,既可以全是抽象方法,也可以全是非抽象方法。一个继承于抽象类的子类,只有实现了父类所有的抽象方法才能够是非抽象类。

5.接口

接口是一个概念。它在C++中用抽象类来实现,在C#和Java中用interface来实现。

接口是专门被继承的。接口存在的意义也是被继承。和C++里的抽象类里的纯虚函数是相同的。不能被实例化。
定义接口的关键字是interface,例如:   
public interface MyInterface{   
public void add(int x,int y);   
public void volume(int x,int y,int z);   
}  

继承接口的关键字是implements,相当于继承类的extends。需要注意的是,当继承一个接口时,接口里的所有函数必须全部被覆盖。
当想继承多个类时,开发程序不允许,报错。这样就要用到接口。因为接口允许多重继承,而类不允许(C++中可以多重继承)。所以就要用到接口。

6.虚基类

在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:
class derive : virtual public base
{
};

虚基类是相对于它的派生类而言的,它本身可以是一个普通的类。只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那类B就只是类A的基类。
虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。
例如 由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。

7. 抽象类VS接口

一个类可以有多个接口,只能继承一个父类??

抽象类可以有构造方法,接口中不能有构造方法;

抽象类中可以有普通成员变量,接口中没有普通成员变量;

接口里边全部方法都必须是abstract的,抽象类的可以有实现了的方法;

抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型;

抽象类中可以包含静态方法,接口中不能包含静态方法;

抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

8. 虚函数VS纯虚函数

虚函数
引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。
纯虚函数
引入原因:
1)同“虚函数”;
2)在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
纯虚函数就是基类只定义了函数体,没有实现过程。
纯虚函数相当于接口,不能直接实例话,需要派生类来实现函数定义;
有的人可能在想,定义这些有什么用?
比如你想描述一些事物的属性给别人,而自己不想去实现,就可以定义为纯虚函数。说的再透彻一些,比如盖楼房,你是老板,你给建筑公司描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的,建筑公司就可以按照你的方法去实现了,如果你不说清楚这些,可能建筑公司不太了解你需要楼房的特性。用纯需函数就可以很好的分工合作了。

二者的区别:

1> 类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了;
纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

2>虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改它的实现;

3>虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以完成自己的实现。纯虚函数的类用于“介面继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的;

4>带纯虚函数的类叫抽象类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用

自动与强制类型转换小总结相关推荐

  1. 【C++基础语法 2】——类型转换[自动类型转换/强制类型转换]

    2. 类型转换 2.1 C++转换方式 自动类型转换(隐式转换):遵循一定的规则,由编译系统自动完成 强制类型转换:把表达式的运算结果强制转换成所需的数据类型 C++自动执行很多类型的转换: 将一种算 ...

  2. java整数能强转转字符,Java中数据类型默认转换和强制类型转换

    默认转换: a:由低到高一次为:(byte   short    char  )---int ---long ---float --- double b:注意:byte   short    char ...

  3. 自动类型转换和强制类型转换

    自动类型转换: 在Java中,任何情况下,整数类型的字面值默认当成int类型处理 小容量可以自动转换成大容量,这种操作被称为自动类型转换 容量大小的定义: 容量大小不是指数据类型的字节数,而是指这个数 ...

  4. java自动转换_java类型转换详解(自动转换和强制转换)

    自动转换 class Hello { public static void main(String[] args) { //自动转换 int a = 5; byte b = 6; int c = a ...

  5. Java中的基本数据类型转换(自动、强制、提升)

    转载自 Java中的基本数据类型转换(自动.强制.提升) 说基本数据类型转换之前,先了解下 Java 中的 8 种基本数据类型,以及它们的占内存的容量大小和表示的范围,如下图所示. 重新温故了下原始数 ...

  6. java类型转换答案,在java中支持两种类型的类型转换,自动类型转换和强制类型转换。父类转化为子类需要强制转换。...

    在java中支持两种类型的类型转换,自动类型转换和强制类型转换.父类转化为子类需要强制转换. 更多相关问题 计算机病毒通过()传染扩散得极快,危害最大. 当一个现象的数量由小变大,另一个现象的数量相反 ...

  7. java的自动类型转换和强制类型转换

    在程序运行时,经常需要将一种数值类型进行转换成另一种类型.下面给出了一个合法的转换. 数值之间的合法转换 上图中有6个实心箭头,表示无信息丢失的转换,有三个虚箭头,表示可能有精度丢失的转换.例如123 ...

  8. Java自动类型转换和强制类型转换

    数据类型的转换可以分为隐式转换(自动类型转换)和显式转换(强制类型转换)两种. 隐式转换(自动类型转换) 满足2 个条件,那么将一种类型的数据赋给另外一种类型变量的时,将执行自动类型转换(automa ...

  9. Java基本数据类型的自动转换_彻底理解Java中的基本数据类型转换(自动、强制、提升)...

    说基本数据类型转换之前,先了解下 Java 中的 8 种基本数据类型,以及它们的占内存的容量大小和表示的范围,如下图所示. 重新温故了下原始数据类型,现在来解释下它们之间的转换关系. 自动类型转换 自 ...

最新文章

  1. 运用.NET读写Windows注册编辑表
  2. python学不会的表情包-python这么简单 为何这么多人学不会
  3. Talend Restful
  4. C#中的DBNull、Null、和String.Empty解释
  5. http header 具体解释
  6. Spring HttpMessageNotReadableException异常
  7. Sitecore 8.2 页面架构设计:模板与组件
  8. 谷歌浏览器弹出Chrome版本太旧解决方式
  9. ES新特性之Reflect对象
  10. cisco route;0x2142和0x2102模式
  11. 逻辑回归实战(动手实践)
  12. php image函数,操作压缩图片时,png图片压缩后整个图片变黑
  13. Java实现电脑屏幕的截取并保存成图片
  14. Java大对象类型的Hibernate映射
  15. 爱读掌阅java版_爱读掌阅app官方下载-爱读掌阅旧版本 - 超好玩
  16. Elastic Stack容器化部署拓展(Https、AD域集成)并收集Cisco设备的日志信息
  17. adb 命令(系统基础类-获取手机系统属性)
  18. 交换机虚拟化和堆叠的区别_交换机链路冗余、链路聚合、堆叠、热备、虚拟化...
  19. 通过CLion 调试JDK源码
  20. QuartusII全编译报错Error: Current license file does not support the EP4CE6E22C8 device

热门文章

  1. 听罗辑思维关于商业模式的思考
  2. 利用树莓派,实现迅雷24小时远程下载
  3. 虚幻四蓝图实战(人物切换汽车控制)
  4. 【蓝桥杯】考前押题--并查集
  5. 如何使用微信小程序视频客服功能?
  6. 常用的计算机有哪些台式的还有哪些,现在台式电脑的主流配置有哪些?
  7. 腾讯、科大讯飞、小恩爱招聘信息
  8. 手撸一个网页版看板(仿照板栗看板样式)
  9. python数据分析怎么学
  10. 【181225】VC++控制文字横向打印和纵向打印的方向源代码