explicit关键字,lambda表达式,static关键字,const关键字,inline关键字,define宏

explicit的作用(如何避免编译器进行隐式类型转换)

作用:声明类构造函数是显式调用的,而非隐式调用,可以阻止调用构造函数时进行隐式转化。只可用于修饰单参数的构造函数,因为无参构造函数和多参构造函数本身就是显式调用的

隐式转换:

#include <iostream>
#include <cstring>

class A{
public:
    int var;
    A(int tmp)
    {
		var = tmp;
    }
};
int main(){
    A ex = 10; //发生了隐式转换
    return 0;
}

上述代码,A ex = 10在编译时,进行了隐式转换,将10转换成了A类型的对象,然后将该对象赋值给了ex。

为了避免隐式转换,可用explicit关键字进行声明。

explicit构造函数只能用于直接初始化

#include <iostream>
#include <cstring>
using namespace std;

class A{
public:
	int var;
	explicit A(int tmp)
	{
		var = tmp;
	}
};
int main(){
	A ex(100);
	A ex1 = 10; //出错,不能使用类型转换构造函数
	return 0;
}

lambda表达式

lambda表达式具有如下形式

[capture list](parameter list)->return type { function body }

其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表。return type,parameter list和function body与任何普通函数一样,分别表示返回类型,参数列表和函数体。与普通函数不同,lambda必须使用尾置返回来指定返回类型

当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。可以这样理解,当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象,传递的参数就是此编译器生成的类类型的未命名对象。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员,类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。

值捕获

采用值捕获的前提是变量可以拷贝,与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。

void fcn1()
{
    size_t v1 = 42; //局部变量
    auto f = [v1]{ return v1; };
    v1 = 0;
    auto j = f(); //j为42,f保存了我们创建它时v1的拷贝;
}

引用捕获

void fcn2(){
	size_t v1 = 42;  //局部变量
	auto f2 = [&v1]{ return v1; };
	v1 = 0;
	auto j = f2(); //j为0;f2保存了v1的引用,而非拷贝
}

一个以引用方式捕获的变量与其他任何类型的引用的行为类似,当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。

引用捕获和返回引用有着相同的问题和限制。如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。

隐式捕获

为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。

如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获。当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。

static

作用:定义静态变量,静态函数。

  • 保持变量内容持久:static作用于局部变量,改变了局部变量的生存周期,使得该变量存在于定义后之后程序运行结束的这段时间。
  • 默认初始化为0,包括未初始化的全局静态变量和局部静态变量。
#include <iostream>
using namespace std;

int func(){
    static int var = 1;
    var += 1;
    return var;
}

int main(){
    for(int i = 0; i < 10; i++)
        cout << func() << " "; // 2 3 4 5 6 7 8 9 10 11
    return 0;
}
  • 隐藏:static作用于全局变量和函数,改变了全局变量和函数的作用域,使得全局变量和函数只能在定义它的文件中使用,在源文件中不具有全局可见性。(注:普通全局变量和函数具有全局可见性,即其他的源文件也可以使用)
  • static作用于类的成员变量和类的成员函数,使得类变量和类成员函数和类有关,也就是说可以不定义类的对象就可以通过类访问这些静态成员。注意:类的静态成员函数中只能访问静态成员变量或者静态成员函数不能将静态成员函数定义成虚函数
#include <iostream>
using namespace std;
class A{
private:
    int var;
    static int s_var; //静态成员变量
public:
	void show(){
        cout << s_var++ << endl;
    }
    static void s_show(){
        cout << s_var <<endl;
        //cout << var << endl; 静态成员函数不能调用非静态成员变量,不能使用this.var
        //show(); 静态成员函数不能调用非静态成员函数。无法使用this.show()
    }
}

静态函数

在函数返回类型前加static,函数即被定义位静态函数,静态函数只能在声明它的文件当中可见,不能被其他文件所用,换句话说,只可以在.cpp文件中使用,不会同其他.cpp中的同名函数引起冲突
所以不要在头文件中声明static全局函数,这样没有意义

静态变量的内存分配

静态变量内存的分配是在程序执行之前,编译时进行内存分配的

在编译时期分配内存和初始化变量意味着程序不必跟踪函数是否调用以及变量是否初始化。具有常量初始值的局部静态变量在本质上与全局变量相同,只是名称仅在该函数的范围内有效。

如果在一个函数中有一个很大的局部静态变量,并且不像为该变量浪费内存,请使用局部静态指针而不是局部静态变量。

局部静态变量的生命周期从程序流第一次遇到局部静态变量的声明时开始,在程序终止时结束。

static在类中使用的注意事项

  • 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不可以出现static关键字和private,public,protected访问规则
  • 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象
  • 静态成员变量可以作为成员函数的参数,而普通成员变量不可以
#include <iostream>
using namespace std;

class A{
public:
    static int s_var;
    int var;
    void fun1(int i = s_var); //正确,静态成员变量可以作为成员函数的参数
    void fun2(int i = var);   //error 非静态成员变量不可以作为函数的参数
}
  • 静态数据成员的类型可以是所属类的类型,而普通数据成员的类型只能是该类类型的指针或引用
#include <iostream>
using namespace std;

class A{
public:
	static A s_var; //静态数据成员
    A var;
    A *p;  //正确,指针
    A& varl;  //正确,引用
}

static静态成员函数

  • 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有this指针,静态成员函数作为类作用域的全局函数
    • 如果要在静态函数中访问类的动态成员(包括成员函数和成员变量),有下面两种方式:
      • 通过类的静态对象来调用。
      • 将类的对象作为参数传递给该静态函数,然后在静态函数中引用这个对象,并调用动态方法。
  • 静态成员函数不能声明成虚函数(virtual)const函数和volatile函数。

为什么static成员函数不能为const函数

  • 当声明一个非静态成员函数为const时,对this有影响。对于一个test类的const修饰的成员函数,this指针相当于test const* const,而对于非const成员函数,this指针相当于test* const。而static成员函数没有this指针,所以使用const来修饰static成员函数没有任何意义。

静态成员函数可以是虚函数么?

静态成员函数不能被声明为virtual函数。
因为

  • static成员不属于任何类对象或类实例,所以即使给此函数加上virtual也没有任何意义
  • 静态与非静态成员函数有一个主要的区别,就是静态成员函数没有this指针,也就没有办法访问虚表指针vptr。虚函数的调用关系:this->vptr->vtable->virtual function

static全局变量和普通全局变量的区别

相同点:

  • 存储方式:普通全局变量和static全局变量都是静态存储方式。

不同点:

  • 作用域:普通全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,普通全局变量在各个源文件中都是有效的;静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误
  • 初始化:静态全局变量只初始化一次,防止在其他文件中使用。

const作用及用法

作用:

  • const修饰成员变量,定义成const常量,相比于宏常量,可进行类型检查,节省内存空间,提高了效率。
  • const修饰函数参数,使得传递过来的函数参数的值不能改变。
  • const修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable修饰的变量除外),也不能调用非const成员函数,因为非const成员函数可能会修改成员变量

在类中的用法:

const成员变量

  • const变量只能在类内声明,定义,在构造函数初始化列表中初始化
  • const成员变量只在某个对象的生命周期内是常量,对于整个类而言却是可变的,不同类的const成员变量的值是不同的,因此不能在类的声明中初始化const成员变量,类的对象还没有创建,编译器不知道它的值。
class Date{
private:
	const int year;
	const int month;
	const int day;
public:
	Date(int y, int m, int d) : year(y), month(m), day(d){}
};

const成员函数

  • 不能修改成员变量的值,除非有mutable修饰;只能访问成员变量
  • 不能调用非常量成员函数,以防修改成员变量的值
#include <iostream>
using namespace std;

class A{
public:
	int var;
    A(int tmp) : var(tmp){}
    void c_fun(int tmp) const //const成员函数
    {
        var = tmp;  //在const成员函数中,不能修改任何类成员变量
        fun(tmp);  //const成员函数不能调用非const成员函数,因为非const成员函数可能会修改成员变量
    }
    void fun(int tmp)
    {
        var = tmp;
    }
}

define和const的区别

区别:

  • 编译阶段:define是在编译预处理阶段进行替换,const是在编译阶段确定其值。
  • 安全性:define定义的宏常量没有数据类型,只是进行简单的替换,不会进行类型安全的检查,const定义的常量是有类型的,是要进行判断的,可以避免一些低级错误。
  • 内存占用:define定义的宏常量,在程序中使用多少次就会有多少次替换,内存中有多个备份,占用的代码段的空间,const定义的常量占用静态存储区的空间,程序运行过程中只有一份。
  • 调试:define定义的宏常量不能调试,因为在预编译阶段就已经进行替换了,const定义的常量可以进行调试。

const的优点:

  • 有数据类型,在定义式可进行安全性检查
  • 可调试
  • 占用较少的空间

define和typedef的区别

  • 原理:#define 作为预处理指令,在编译预处理时进行替换操作,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。typedef是关键字,在编译时处理,有类型检查功能,用来给一个已经存在的类型一个别名。
  • 功能:typedef用来定义类型的别名,方便使用。#define不仅可以为类型取别名,还可以定义常量,变量,编译开关等。
  • 作用域:#define没有作用域的限制,只要是在之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。
  • 指针的操作:typedef和#define在处理指针时不完全一样。
  • #define不是语句,不在末尾加分号;typedef是语句,要加分号标识结束
#include <iostream>
#define INTPTR1 int*;
typedef int* INTPTR2;

using namespace std;

int main(){
    INTPTR1 p1, p2; //p1: int*; p2:int
    INTPTR2 p3, p4; //p3: int*; p4:int*
   	int var = 1;
    const INTPTR1 p5 = &var; //相当于const int* p5;常量指针,即不可以通过p5去修改p5指向的内容,但是p5可以指向其他内容
    const INTPTR2 p6 = &var; //相当于int* const p6;指针常量,不可使p6再指向其他内容
	return 0;
}

inline作用及其使用方法

作用:

inline是一个关键字,可以用于定义内联函数。内联函数,像普通函数一样被调用,但是在调用时并不通过函数的调用机制而是直接在调用点出展开,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。

使用方法:

  • 类内定义成员函数默认是内联函数。

    在类内定义成员函数,可以不用再函数头部加上inline关键字,因为编译器会自动将类内定义的函数(构造函数,析构函数,普通成员函数等)声明为内联函数。

  • 类外定义成员函数,若想定义为内联函数,需要用关键字inline声明

    当在类内声明函数,在类外定义函数时,如果想将该函数定义为内联函数,则可以在类内声明时不加inline关键字,而在类外定义函数时加上inline关键字。

  • 另外,可以在声明函数和定义函数的同时加上inline;也可以只在函数声明时加上inline,而定义函数时不加inline。只要确保在调用该函数之前把inline的信息告知编译器即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值