c语言如何使用父类的构造方法(C宽窄基本类型转换和父类子类转换)
c语言如何使用父类的构造方法(C宽窄基本类型转换和父类子类转换)保存值的隐式转换通常称为提升。在执行算术运算之前,整数提升用于从较短的整数类型中创建整数(ints)。这反映了这些提升的最初目的:将操作数调整为算术运算的“自然”大小(sizeof(int))。此外,float到double被认为是一种提升。 unsigned char a = 0xa5; unsigned char b = ~a>>4 1; printf("%d\n" b); // 250 // 不考虑类型提升 考虑类型提升 // 10100101 0000000000000000000000000000000010100101 // 01011010 11111111111111111111111111101011010 // 00
基本数据类型有不同的编码方案(encoding scheme),需要不同的字节长度来适应各自的值域。
基本类型的宽窄转换,是一个整体的转换,其由窄到宽是安全的,由宽到窄可能会丢失数据。
继承链上父类子类转换,不单只是数据域的宽窄问题,还有不同数量的成员函数去访问数据的问题。
1 基本类型的转换C 虽然是强类型语言,但支持类型的隐式转换和显式转换。类型转换不仅发生在表达式中,还发生在函数调用时的实参与形参结合时,以及函数返回值时。
1.1 基本类型由窄到宽,其隐式转换为安全的
char ch = 3;
int n = ch;
double f = n;
1.2 类型提升
表达式(右式)中不同类型参与运算时,会有一个类型提升的问题(避免数据丢失),如整型提升到最大宽度的类型,有符号提升到无符号,单精度提升到双精度等。
unsigned char a = 0xa5;
unsigned char b = ~a>>4 1;
printf("%d\n" b); // 250
// 不考虑类型提升 考虑类型提升
// 10100101 0000000000000000000000000000000010100101
// 01011010 11111111111111111111111111101011010
// 00000010 11111010
The implicit conversions that preserve values are commonly referred to as promotions. Before an arithmetic operation is performed integral promotion is used to create ints out of shorter integer types. This reflects the original purpose of these promotions: to bring operands to the “natural” size for arithmetic operations. In addition float to double is considered a promotion.
保存值的隐式转换通常称为提升。在执行算术运算之前,整数提升用于从较短的整数类型中创建整数(ints)。这反映了这些提升的最初目的:将操作数调整为算术运算的“自然”大小(sizeof(int))。此外,float到double被认为是一种提升。
1.3 基本类型由宽到窄(narrowing conversions)
In C a narrowing conversion is a numeric conversion that may result in the loss of data. Such narrowing conversions include:
在C 中,窄向转换是一种可能导致数据丢失的数字转换。此类窄向转换包括:
① From a floating point type to an integral type.
从浮点类型到整数类型。
② From a wider floating point type to a narrower floating point type unless the value being converted is constexpr and is in range of the destination type (even if the narrower type doesn’t have the precision to store the whole number).
从较宽的浮点类型到较窄的浮点类型,除非转换的值是constexpr并且在目标类型的范围内(即使较窄的类型没有存储整数的精度)。
③ From an integral to a floating point type unless the value being converted is constexpr and is in range of the destination type and can be converted back into the original type without data loss.
从整数类型转换为浮点类型,除非转换的值是constexpr,并且在目标类型的范围内,并且可以在不丢失数据的情况下转换回原始类型。
④ From a wider integral type to a narrower integral type unless the value being converted is constexpr and after integral promotion will fit into the destination type.
从较宽的整数类型转换为较窄的整数类型,除非转换的值是constexpr,并且整数升级后将适合目标类型。
The good news is that you don’t need to remember these. Your compiler will usually issue a warning (or error) when it determines that an implicit narrowing conversion is required.
好消息是你不需要记住这些。当编译器确定需要窄向转换时,通常会发出警告(或错误)。
Recommend to using static_cast to make narrowing conversions explicit.
推荐使用static_cast显式进行窄向转换。
Compilers will often issue warnings when a potentially unsafe (narrowing) implicit type conversion is performed. For example consider the following program:
当执行潜在的不安全(缩小范围)隐式类型转换时,编译器通常会发出警告。例如,考虑以下程序:
int i { 48 };
char ch = i; // implicit narrowing conversion
Casting an int (2 or 4 bytes) to a char (1 byte) is potentially unsafe (as the compiler can’t tell whether the integer value will overflow the range of the char or not) and so the compiler will typically print a warning. If we used list initialization the compiler would yield an error.
将int(2或4字节)转换为char(1字节)可能不安全(因为编译器无法判断整数值是否会溢出char的范围),因此编译器通常会打印警告。如果使用列表初始化,编译器将产生错误。
To get around this we can use a static cast to explicitly convert our integer to a char:
为了解决这个问题,我们可以使用静态转换将整数显式转换为字符:
int i { 48 };
// explicit conversion from int to char so that a char is assigned to variable ch
char ch { static_cast<char>(i) };
When we do this we’re explicitly telling the compiler that this conversion is intended and we accept responsibility for the consequences (e.g. overflowing the range of a char if that happens). Since the output of this static_cast is of type char the initialization of variable ch doesn’t generate any type mismatches and hence no warnings or errors.
当我们这样做的时候,我们明确地告诉编译器这个转换是有意的,并且我们承担后果的责任(例如,如果发生这种情况,就会溢出字符的范围)。由于此static_cast的输出是char类型,因此变量ch的初始化不会生成任何类型不匹配,因此不会产生警告或错误。
1.3.1 长整型转换为短整型:舍弃高位;
1.3.2 浮点型转换为整型:舍弃小数部分;
1.3.3 double转换为float:可能会有精度损失;
#include <stdio.h>
int main()
{
int n = 1027; // 0000 0000 0000 0000 0100 0000 0011
char ch = n; // 0000 0011 ch与n的编码方式相同(补码),直接取sizeof(ch)个字节
// be reduced modulo (the remainder of an integer division by the) char’s range
printf("%d\n" ch); // 3
double f = 3.75; // 11.11b 0100 0000 0111 0000 0000 0000 0000 0000
// 指数 100 0000 01, 尾数 11
n = f; // n与f的编码方式不同(补码与IEEE754浮点编码方案),按语言预定规则转换
printf("%d\n" n); // 3 弃掉小数部分
int a = 1000; // 0000 0000 0000 0000 0011 1110 1000
char b = a; // b becomes –24 (on some machines)
// 取最后一个字节(小端存储则地址不变,取第一个字节),
printf("%d\n" b);// 1110 1000 = -11000 = -24
}
// a double-to-int conversion truncates (always
// rounds down toward zero) rather than using the conventional 4/5 rounding.
// how conversions from double to int and conversions from int to char
// are done on your machine
C 11 introduced an initialization notation that outlaws narrowing conversions.
C 11引入了一种初始化符号,禁止窄向转换。
For example we could (and should) rewrite the troublesome examples above using a {}-list notation rather than the = notation:
例如,我们可以(而且应该)使用{}-list表示法而不是=表示法重写上述麻烦的示例:
double x {2.7}; // OK
int y {x}; // error: double -> int might narrow
int a {1000}; // OK
char b {a}; // error: int -> char might narrow
int char b1 {1000}; // error: narrowing (assuming 8-bit chars)
char b2 {48}; // OK
We can use narrow_cast when we need to convert a value and we are not sure “if it will fit”; it is defined in std_lib_facilities. h and implemented using error(). narrow_cast<> can throw a runtime_error exception:
当我们需要转换一个值并且我们不确定“它是否合适”时,我们可以使用narrow_cast;它在std_lib_facilities. h中定义,并使用error()实现。narrow_cast<>可以引发runtime_error异常:
int x1 = narrow_cast<int>(2.9); // throws
int x2 = narrow_cast<int>(2.0); // OK
char c1 = narrow_cast<char>(1066); // throws
char c2 = narrow_cast<char>(85); // OK
2 继承链上父类子类转换
子类对象 ← 父类对象,子可能有更多成员,可能存在越界;
父类对象 ← 子类对象,父可能只有更少空间,存在切割;
父类指针或引用 ← 子类指针或引用,不存在切割,且是多态的必备条件。
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增的成员体现了其个性。
code:
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(string sn int n char s);
~Student();
void dis();
private:
string name;
int num;
char sex;
};
Student::Student(string sn int n char s)
:name(sn) num(n) sex(s){}
Student::~Student(){}
void Student:: dis()
{
cout<<name<<endl;
cout<<num<<endl;
cout<<sex<<endl;
}
class Graduate:public Student
{
public:
Graduate(string sn int in char cs float fs);
~Graduate();
void dump()
{
dis();
cout<<salary<<endl;
}
private:
float salary;
};
Graduate::Graduate(string sn int in char cs float fs)
:Student(sn in cs) salary(fs){}
Graduate::~Graduate(){}
class Birthday
{
public:
Birthday(int y int m int d);
~Birthday();
void print();
private:
int year;
int month;
int day;
};
Birthday::Birthday(int y int m int d)
:year(y) month(m) day(d){}
Birthday::~Birthday(){}
void Birthday::print()
{
cout<<year<<month<<day<<endl;
}
class Doctor:public Graduate
{
public:
Doctor(string sn int in char cs float fs string st int iy int im int id);
~Doctor();
void disdump();
private:
string title; //调用的默认构造器,初始化为””
Birthday birth; //类中声明的类对象
};
Doctor::Doctor(string sn int in char cs float fs string st int iy
int im int id)
:Graduate(sn in cs fs) birth(iy im id) title(st){}
Doctor::~Doctor(){}
void Doctor::disdump()
{
dump();
cout<<title<<endl;
birth.print();
}
int main()
{
Student s("zhaosi" 2001 'm');
s.dis();
cout<<"----------------"<<endl;
Graduate g("liuneng" 2001 'x' 2000);
g.dump();
cout<<"----------------"<<endl;
Doctor d("qiuxiang" 2001 'y' 3000 "doctor" 2001 8 16);
d.disdump();
getchar();
return 0;
}
/*
zhaosi
2001
m
----------------
liuneng
2001
x
2000
----------------
qiuxiang
2001
y
3000
doctor
2001816
*/
As you have already seen an object can be cast or assigned to its parent class. If the cast or assignment is performed on a plain old object this results in slicing:
正如您已经看到的,可以强制转换对象或将其指定给其父类。如果在普通旧对象上执行强制转换或指定,则会导致切片:
Base myBase = myDerived; // Slicing!
Slicing occurs in situations like this because the end result is a Base object and Base objects lack the additional functionality defined in the Derived class. However slicing does not occur if a derived class is assigned to a pointer or reference to its base class:
切片发生在这样的情况下,因为最终结果是一个基对象,而基对象缺少派生类中定义的其他功能。但是,如果将派生类分配给指针或对其基类的引用,则不会发生切片:
Base& myBase = myDerived; // No slicing!
This is generally the correct way to refer to a derived class in terms of its base class also called upcasting. This is why it’s always a good idea to make your methods and functions take references to classes instead of directly using objects of those classes. By using references derived classes can be passed in without slicing.
这通常是根据派生类的基类(也称为向上转换)引用派生类的正确方法。这就是为什么让方法和函数引用类而不是直接使用这些类的对象总是一个好主意。通过使用引用,可以传入派生类而无需切片。
Casting from a base class to one of its derived classes also called downcasting is often frowned upon by professional C programmers because there is no guarantee that the object really belongs to that derived class and because downcasting is a sign of bad design. For example consider the following code:
从基类到其派生类之一的强制转换(也称为向下转换)通常受到专业C 程序员的反对,因为无法保证对象确实属于该派生类,而且向下转换是糟糕设计的标志。例如,考虑以下代码:
void presumptuous(Base* base)
{
Derived* myDerived = static_cast<Derived*>(base);
// Proceed to access Derived methods on myDerived.
}
If the author of presumptuous() also writes code that calls presumptuous() everything will probably be okay because the author knows that the function expects the argument to be of type Derived*. However if other programmers call presumptuous() they might pass in a Base*. There are no compile-time checks that can be done to enforce the type of the argument and the function blindly assumes that base is actually a pointer to a Derived.
如果presumptuous()的作者还编写了调用presumptuous()的代码,那么一切都可能正常,因为作者知道函数期望参数的类型是派生的*。然而,如果其他程序员调用presumptuous(),他们可能会传入一个Base*。没有可以执行的编译时检查来强制参数的类型,并且函数盲目地假设base实际上是指向派生类型的指针。
Downcasting is sometimes necessary and you can use it effectively in controlled circumstances. However if you are going to downcast you should use a dynamic_cast() which uses the object’s built-in knowledge of its type to refuse a cast that doesn’t make sense. This built-in knowledge typically resides in the vtable which means that dynamic_cast() works only for objects with a vtable that is objects with at least one virtual member. If a dynamic_cast() fails on a pointer the pointer’s value will be nullptr instead of pointing to nonsensical data. If a dynamic_cast() fails on an object reference an std::bad_cast exception will be thrown.
向下转型有时是必要的,您可以在受控环境中有效地使用它。但是,如果要向下转型,则应使用dynamic_cast(),它使用对象的内置类型知识来拒绝没有意义的转换。这种内置知识通常驻留在vtable中,这意味着dynamic_cast()仅适用于具有vtable的对象,即至少具有一个虚拟成员的对象。如果指针上的dynamic_cast()失败,指针的值将为nullptr,而不是指向无意义的数据。如果对象引用上的动态dynamic_cast()失败,将引发std::bad_cast异常。
The previous example could have been written as follows:
前面的示例可以编写如下:
void lessPresumptuous(Base* base)
{
Derived* myDerived = dynamic_cast<Derived*>(base);
if(myDerived != nullptr) {
// Proceed to access Derived methods on myDerived.
}
}
The use of downcasting is often a sign of a bad design. You should rethink and modify your design so that downcasting can be avoided. For example the lessPresumptuous() function only really works with Derived objects so instead of accepting a Base pointer it should simply accept a Derived pointer. This eliminates the need for any downcasting. If the function should work with different derived classes all inheriting from Base then look for a solution that uses polymorphism.
使用向下转型通常是糟糕设计的标志。您应该重新考虑和修改您的设计,以避免向下转型。例如,lessPresumptuous()函数实际上只适用于派生对象,因此它不应该接受基指针,而应该只接受派生指针。这消除了任何向下转型的需要。如果函数应该使用不同的派生类,所有派生类都是从基继承的,那么请寻找使用多态性的解决方案。
ref
https://www.learncpp.com/cpp-tutorial/numeric-conversions/
Marc Gregoire 《PROFESSIONAL C 》(比)格莱戈尔《C 高级编程 》
-End-