c++关键字(下)

c++关键字(下),第1张

c++关键字(下) cast

        cast转换包含多个类型转换,在c中类型转换有两种,如果是允许互相转换的类型,编译器就直接隐式类型转换了,不允许的,可以强制类型转换,这种其实有一些一刀切的味道。c++为了避免这种一刀切,把强制类型转换进行了细分。隐式可以由程序员自己转换使用static_cast、不兼容类型转换使用reinterpret_cast,而const类的转换使用const_cast,需要动态确认的使用dynamic_cast转换。

static_cast

        static_cast第一个功能是,编译时的静态类型检查,static_cast并不能保证代码的正确性,如精度丢失,类型错误解引用等,static_cast是一种初级语法特性,只能解决初级问题,在c的高版本中已经有了该关键字,但是基本没啥用。        static_cast第一个功能是,隐式类型转换,当我们对类型不同的两个变量间赋值时,通常编译器会帮我们做隐式类型转换的同时报警告,或者直接报错误,有时候我们就是需要直接对不同类型的变量做赋值 *** 作,明确的告诉编译器这是我故意的,你不用管,就可以在变量前添加,static_cast<>,使用方法如下:

float a=222.2;
char b=static_cast (a);

        在指针领域常用static_cast来对void *p类型进行转换,示例如下,这种应用在驱动中使用的比较广泛

void *p1 =&a;
int *p2 = static_cast (p1);
与强制类型转换比较

        static_cast与c中的强制类型转换类似,但威力不同,强制类型转换属于为所欲为,编译器完全不监管,对错由程序员自己负责,而static_cast是被编译器监管的,只在类型兼容的情况下放行。示例如下:

int aa=50;
char *pp = (char *)&aa;  //强制类型转换,编译器允许
char *pp1 = static_cast (&aa);//编译器不允许
reinterpret_cast

        reinterpret释义为“重新解释”,也就是指定对内存空间的解析方法,如把int型的空间使用char型规则去解释,空间内容没有发生变化,变化的只是内存空间的解析的方法,如果说static_cast是一个小号强制类型转换,那么reinterpret_cast就是一个大号强制类型转换,它可以进行类型强制转换,示例如下:

unsigned char *p = (unsigned char *)0x50123456;     //强制类型转换
unsigned char *p1 = reinterpret_cast(0x50123456);//编译器允许
const_cast

        const_cast是用来把对const与非const进行互转的,用得多的就是const转非const,我们知道在c中const修饰的变量其实还是在内存中,从物理特性来讲,内存的值都是可以被修改的,之所以我们不能修改,其实是编译器在拦截这种 *** 作,我们完全可以通过指针指向变量的地址,再通过指针去修改内存的值,这种方法在c中时完全没问题的,但在c++中情况就发生了变化,如下示例:

const int a=5;
int *p=(int *)&a; 
int *p1=const_cast(&a); //与上一句等效,访问const变量。
*p=6;
cout << "a= " << a <函数传参修改,当我们要把const修饰的变量传址给一个不带const的函数形参时,直接地址传递是不允许的,必须要把const属性去掉,示例如下:

int add(int *a,int *b);  //一个函数声明
const int c=6;  const int d=7;    //定义了两个const变量
add(const_cast(&c),const_cast(&d)); //const_cast修饰后再传递。
dynamic_cast

        dynamic释义动态,前面我们讲到的const_cast、static_cast、reinterpret_cast都是在编译阶段进行转换,而dynamic_cast不同,是在运行时才进行转换,他具有不确定性,转换失败会返回一个NULL,成功就返回有效对象。只用在父子class的指针和引用访问时的转换中,尤其是下行转换,实际工作中,运行时确定对象类型是一种需求,所以c++有这么一套机制来解决问题。

auto

        auto是c的一个关键字,用来修饰局部变量,然而基本没人用,所以c++对auto进行了重定义,其功能是自动推导出变量的类型,他的推导是基于我们定义时给的初值的类型,所以定义auto变量时必须有初值,他也可以一次定义多个变量,但是要求变量初值类型是相同的。示例如下:

auto  a=5,b=6;
cout << “a type:” << typeid(a).name << endl  //输出int类型
decltype

        decltype的的作用是判断一个表达式的类型,并新定义一个类型相同的变量,定义时可以有初值,也可以无初值,示例如下:

int a;
decltype(a) c;
cout << "c type : " < 
auto与decltype比较 
  • auto忽略顶层const,而decltype则会保留,示例如下:
const int a=5;
auto b=a;          //自动推导定义一个类型相同的变量。
b=8;              //允许 *** 作,b不带const属性
decltype(a) c=7; //计算a的类型,并定义一个与a类型相同的变量c
//c=9;          //编译报错,c具有cosnt属性。
  • auto是作用类型占用符(好像一个类型),而decltype用法类似于sizeof运算符,他接收一个参数,然后去计算参数的类型,做为返回值进行引用类型定义变量。
  • 对引用和解引用的 *** 作,auto会对顺着引用找到原始类型,而decyltype推断到引用就截止了,就直接使用引用。
class & private & protected & public

        后续我们会有章节单独讲解class,所以在此我们只对class中的一些关键字简单的了解,不深入。

        struct是结构体,class是一个增强版struct,如果我们只想封装数据就用struct,如果我们想面向对象就使用class。

        类是对数据和方法的封装,其中有些东西我们不希望别篡改,有些我们不希望别看到,所以c++为类设计了权限管理,权限管理涉及到三个关键字:

  • private—私有的,自用的。
  • protected—受保护的
  • public—公开的
new & delete

        c++中将c中的变量升级成了对象,因为涉及到构造函数和析构函数,所以c中的malloc升级成了new,free升级成了delete。虽然在c++中仍然使用很多全局和局部变量等静态分配内存变量,但是使用动态内存的变量占比也变多了,这是业务决定的,不是语言本身决定的。

static

        在c++中对static新增了一个特性,在class中的static用来修饰成员变量或方法属于class本身,而没有static修饰的属于对象。静态类往往用在单例模式。实际上和面向对象的思想有所违背。

this

        this释义这个,本质上是一个指针。指向当前对象,主要的作用是让我们在未定义对象前可以在方法中去调用对象里的成员。示例如下:

cout<< "C_A::a = " << this -> a < virtual

        virtual修饰class的成员函数为虚函数,一般在基类中,只有接口声明没有实体定义。基类的成员的virtual成员函数可以在派生类中override重写,以实现面向对象的多态特性。

override

        重写关键字override与重载关键字overload注意区分,override用来在派生类中成员函数声明时明确表明需要派生类去重写的那些成员方法,这样如果程序员在成员方法实体定义中做的不对编译器可以报错提醒。

final

        继承终止,一个class不想被继承(不想做父类),使用finanl来修饰,同样一个成员方法不希望被子类override,也可以使用final来修饰。

using

        using有两种用法,一种是using namespace std这种,另外一种是与class的继承和访问限制有关,属于一个声明,能够让private继承的子类去声明并访问父类中本来无权访问的成员。

operator

        运算符重载,也就是为一个class重定义某种运算符。例如我们cout使用的<<左移符号就是被运算符重载后的结果。

friend

        让一个不属于class的外部函数也能访问class内部受保护的成员变量,实际上可以说是对面向对象的一种破坏,本来我们使用了权限来控制,结果friend是打破这种控制。

explicit

        释义为显式的,用来修饰只有一个参数的构造函数,以组织构造函数不合时宜的类型转换。

const

        const在c中一直都有,我们也经常在使用,const变量和宏的功能有些类似,不同的是宏是不占内存的,在预编译的时候就已经被替换掉了,而const是占用内存的,const编译时会被检查,而宏不被检查,const除了定义变量外也可以定义数组,c语言中const常用的方法有如下3种:

const int *p //p指向的变量不能被改变
int * const p//指针不能被改变,和引用一样。
const int * const p//都不能被改变

在c++中新增了2总语法,const引用和const成员函数

const与引用

主要用于函数传参,限制函数内部对实参修改,如下示例:

void func(const int &i)  //明确说明程序中不会修改i

const跟随在成员函数后面,限制函数内部对成员变量的修改。如下示例:

void C_A::func(void) const  //什么与定义都需要同时加const
{
       this ->i = 1;   //语法错误,不允许修改任何成员变量
       cout << "i= " << this -> i << endl;
}
mutable

        前面我们刚讲了const在成员变量中修饰成员函数不能修改成员变量,而mutable来拆台,他就是来打破const的限制的。当我们在成员函数添加了const后,该成员函数不能修改所有class中的成员变量,但是有一种变量是例外的,就是被mutable修饰的成员变量是可以修改的,有时候我们就是有这样的需求,大部分的变量不修改,其中有一个要修改,示例如下:

class C_A
{
    public:
    mutable int cnt;  //允许func函数修改。
    void func(void) const; //不会修改任何成员变量,mutable修饰的除外。
};
constexpr

        constexpr本质上是让编译器在编译时将我们的程序进行执行,运行结果直接放在我们的表达式中,来增加运行时的效率,由c++11引入,实际上有些编译器并不支持,需要测试,其用法如下:

constexpr int add(int a,int b) //声明一个函数,函数就是求两输入参数的和。
int num = add(10,10);  //函数调用

        经过上面的函数后,编译器编译时会直接把代码优化为int num = 20;这就是cosntexpr的作用,让编译器执行程序。

template & typename

        模(mu)板编程初体验,先不讨论什么是模板,先讨论个需求,现在需要编写一个程序实现各种数据类型的求和函数,因为数据类型很多,常规的做法是我们需要对每一个数据类型都写一个函数,再由c++的函数重载来实现调用同名但类型不同的函数,想想我都头大,好麻烦。

        为了解决这个问题,c++提出了一个模板解决方案,只需要写一个函数,函数类型我们使用特殊方式来指定即可让一个函数来完成不同类型的数据求和,示例如下:

template
T add(T a,T b)
{
return (a+b);
}

        以上代码中“T”来代表数据类型,理论上我们可以找任何字母来代替,然而行业的潜规则是使用template的首字母大写T。如果你不想独树一帜,应该遵循这个潜规则。

export

        用来在cpp文件中定义一个模板类或模板函数,而他的声明在对应的头文件中。export是专用于模板的,类似于extern声明简单的类型变量。该关键字有些编译器不认识,如我们使用的gcc就不支持。

try & catch & throw

        异常处理,在c中没有异常处理机制,一旦程序出现问题就直接崩溃,在c++或一些高级语言中都增加了错误处理机制,可以在程序运行时拦截错误并处理,这样程序就不会异常终止。应用示例如下:

try                      //括号内的代码被监控
{
    if(b==0) 
    {
        throw -1;      //满足设定的错误条件,抛出int型-1
    }
    return (a/b);      //上部被抛出,本代码得不到运行
}
catch(int)            //抛出的是int型,执行本代码
{
    cout
}

        这段代码看似很麻烦,还不如c中使用if直接判断来得简单,是因为我们假设的问题太简单,功能复杂时才能看出他的优秀,例如我们去读取一个文件,可能会出现打开失败、读取失败、内存申请失败等,要是我们全部都使用if去嵌套判断那就太复杂了,如果使用异常处理机制来实现就会简单明了。我们调用一些库函数时,他们默认也会抛出一些异常,所以我们要学会处理。

异常和函数

        throw一个异常,如果没有cath捕捉到,throw会按函数的调用关系倒着一层层的向外传递,直到被cath到为止。

        当我们去调用一个库函数时,这些函数可能内部会抛出异常,通常库函数内部是不会处理异常的,都是抛出给使用库的人来处理,同时在函数名后面会用throw列表来显示出来,其列表如下:

int divf(int a,int b) throw(int, char, float, duble);//函数定义时给出列表
标准库中的exception类

        exception类是标准库中定义的一个异常类及其派生类,一些常见的抛出的错误类型都被包含在其内部,例如我们typeid一个指针类型,而这个指针的值为NULL时,就会抛出一个bad_typeid错误类,又或者我们使用dynamic_cast进行类型动态转换时,如果转换不安全,也会抛出一个bad_type错误类等。

noexcept

        我们的代码一般会在函数后面使用throw(int , char )这种方式类列出会抛出的错误类型,如果我们函数根本就不会抛出任何错误,应使用noexcept来标识,如果函数后面什么都没有,结论就比较复杂,可能没有任何抛出,也可能会抛出任何类型,那简直就是一个麻烦事情,所以我们写代码时应明确,没有就是没有,不要啥都不说。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5504199.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-13

发表评论

登录后才能评论

评论列表(0条)

保存