C++之引用常量,引用做参数,引用做返回值,,const引用,内联函数(),友元函数,static修饰的类的静态成员

引用:书上的概念是这样的:引用是某个已知变量或对象的别名。引用不是变量,它自身没有值,也没有地址,他不占用内存空间。
创建引用:格式:<类型>&<引用名> = <初始值>;
        int a = 1;
	int& b = a;
	int& c = a;
	int& d = a;
	cout<<&a<<" "<<&b<<" "<<&c<<" "<<&d<<endl;
	cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
 
 
引用的特点:①一个变量可以有多个别名;
           ②引用必须初始化;
           ③引用只能在初始化时引用一次(否则就是简单的把原来引用的变量进行了赋值);
还有一种引用的方式:使用关键字:const(即可以引用常量,也可以引用变量,但是不能通过这个变量来修改值)
        int a = 1;
	int& b = a;
	const int& c = a;
	const int& d = 10;
但是,在下面这种情况是 不可以的
const int a = 10;
int& b = a;  //错误的,是权限的放大。
下面这样的情况呢?
int d = 10;
double e = d;   //隐式类型的转换。
总结:隐式类型转化和类型提升的区别:
当两个数类型不同时:当范围小的数和范围大的数进行比较时,范围小的就会往范围大的数进行提升,然后比较。
隐式类型转化:相近类型之间的转换。相近类型:类如:char和int,double和int等等。
但是,不可以直接用不同类型的引用直接,却可以通过const来引用
	int a = 1;
	int& b = a;
	double& c = a;     //这里会报错----->隐式类型转换,中间有临时变量,临时变量具有常性。
	const double& d = a;   //这个确实正确的
来解释一下用不同类型的引用为什么会报错呢?我们把以上程序转到反汇编来看:
所以我们发现引用的过程中还是采用了eax这个临时变量来实现。
但是这个临时变量具有常性。但是就像上面的例子一样,通过不同的类型引用:double& c = a;如果c不是一个常量,那么就允许对c进行赋值,但是在这个过程中要通过一个临时变量来实现,但是这个临时变量具有常性,怎么可以用一个常量给double&进行赋值ne?所以就会报错。
相同的下面这个代码也是错误的:
 
 
const int a = 1;int& b = a; 错误

但是这样就是正确的:

	int a = 1;
	const int& b = a;
所以:权限可以缩小,不可以放大。

引用的应用

当然,引用(&)也可以在函数返回值时使用
int Big(int a,int b)
{
	if(a > b)
	{
		return a;
	}
	return b;
}
int main()
{
	const int& r = Big(3,5);
	cout<<r<<endl;
	system("pause");
	return 0;
}

但是如果我们在主函数中接收返回值时去掉const,程序就会报错。原因和上面的原因相同,在返回时会生成一个临时变量(在寄存器中),而临时变量具有常性。
但是如果我们使用下面的程序:
int& Big(int a,int b)
{
	if(a > b)
	{
		return a;
	}
	return b;
}
int main()
{
	int& r = Big(3,5);
	system("pause");
	return 0;
}
int& r = Big(3,5);  //结果是5
Big(10,5); //结果是10;
这个打印的结果就是对的,因为它就是用引用来返回的。在返回过程中,是将地址放在临时变量(寄存器中),然后在main函数中进行解引用取值,函数出了栈帧,再去使用它,就会存在越界现象,但是越界只是再该地址的后面设置几个标志位,但是标志位的个数有限,所以当越界多时,并不会检测到。

用引用做参数

void Swap(int& a,int& b)
{
     int tmp = a;
     a = b;
     b = tmp;
}
int main()
{
     int a = 10;
     int b = 20;
     Swap(a,b);
     return 0;
}
作用:1.在函数体改变了,在函数外也改变了。

2.如果是很大的,在传值时就是拷贝,就会浪费空间和造成效率低。所以可以传引用,但是传引用时,在函数体内改变了,就会导致在函数体外也会被改变,因此可以在引用的参数前加const.

总结一下:引用从语法层讲,就是别名,不开辟空间,不占用内存,节省空间,提高效率。但是,我们反观反汇编就会发现其实还是创建了一个临时变量来交换它的值,所以,从这里讲,引用和指针很相似。

sizeof(引用)------>是变量的大小。

sizeof(指针)------->是4(32位系统)。

指针(++/--)------->加/减类型的大小。

引用(++/--)-------->加/减1.

C++:就是一种面向对象兼面向过程的语言。因为C++是从C发展而来,所以面向过程其实就是继承于C语言。
什么叫做对象?
对象就是类的实例化,类就是同时将 数据和数据相关的操作(函数)封装在一起。
类的成员包括成员变量和成员函数,它们都可以通过:public,private,protect控制访问限定符来修饰。如果类中没有标明限定符,那么默认就是private类。 限定符的作用范围是到下一个限定符或者大括号。
class Student
{
public:
	void Show()
	{
		cout<<_name<<" is "<<age<<endl;
	}
public:
	char* _name;
	int age;
};
int main()
{
	Student A;
	A._name = "huhu";
	A.age = 6;
	A.Show ();
	system("pause");
	return 0;
}
其实,类看着和C语言中的结构体非常相似。但是结构体只可以包含数据。那么类的大小怎么来计算呢?
上面的这个类的大小是:
可以看出: 这个类的大小它只是计算了那个指针和整形的大小。并没有包含函数的大小,所以,在类中计算大小时是不包含函数的。同样,也存在内存对齐。为什么要有内存对齐这个东西呢,在window下,内存对齐为8,Linux中内存对齐为4.内存对齐的优点是:提高了内存的访问速度,而缺点是浪费了空间,就是典型的以空间换时间的存在。在访问内存时,并不是每次按一个字节一个字节的访问,而是以2的倍数,4的倍数或者其它的。。。。所有在内存中中存放自定义类型时,一般都是存在内存对齐的,加快了内存的访问速度。那么, 在结构体中是不允许存在空结构体的,但是却可以存在空类。而且空类占了一个字节。

内联函数(inline)

以inline修饰的函数叫做内联函数,C++编译时会在调用内联函数的地方进行展开,没有函数压栈的开销,提高了程序的运行效率。

内联函数的调用过程:在调用一个内联函数时,编译器首先会检查调用是否正确(如:进行类型安全检查)。如果正确,内联函数的代码会直接替换函数调用,所以就省去了函数调用栈帧的开销。这里与预处理有显著的区别,因为预处理器不能进行类型安全的检查。

特点:

  1. inline函数是一种以空间换时间的做法,省去函数在调用过程中栈帧的开销
  2. inline必须与定义放在一起,才能成为内联函数。仅与声明放在一起是不起任何作用的。
  3. 定义在类内成员函数默认为内联函数。
  4. inline对于编译器而言只是一种建议,编译器会自动优化。当inline函数内部有循环/递归等,编译器优化时会忽略掉内联。

缺点:内联函数是以代码膨胀为代价的,仅仅省去了函数栈帧的开销。如果执行函数体内代码的时间,相较于函数调用的开销大,那么效率反而会降低。另一方面,每一处内联函数调用都会复制代码,将程序的代码量增大,消耗更多的内存空间。

下面我们写一个实例:

class AA
{
public:
	void Show()    //定义在类内的成员函数默认为内联函数
	{
		cout<<"Show()"<<endl;	
	}
	void Display();
private:
	int _a;
	int _b;
};
inline void AA::Display()    //inline关键字要与定义放在一起
{
	cout<<_a<<endl;
	cout<<_b<<endl;
}

友元函数

关键字:friend;

特点:在C++中,友元函数允许在类外访问类内的任何成员,就像成员函数一样。

强调:

  1. 友元函数不是类的成员函数;
  2. 友元函数可以通过类的对象访问任何成员,包括私有和保护。
class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	friend void Display(const Date& d);
	friend ostream& operator<<(ostream& out,const Date& d);      //重载<<符号
	friend istream& operator>>(istream& in,Date& d);      // 重载<<符号
private:
	int _year;
	int _month;
	int _day;
};
void Display(const Date& d)
{
	cout<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;
}
ostream& operator<<(ostream& out,const Date& d)
{
	out<<"年:"<<d._year <<endl;
	out<<"月:"<<d._month <<endl;
	out<<"日:"<<d._day <<endl;
	return out;
}
istream& operator>>(istream& in,Date& d)
{
	in>>d._year ;
	in>>d._month ;
	in>>d._day ;
	return in;
}

注意:在重载<<和>>符号时,就必须用到友元函数了,因为在成员函数中会有this指针,而this指针总是第一个参数,而<<和>>符号需要两个参数,而当是<<符号时,为了提高代码的可读性,就要把类的对象放在右边,但是this指针却是第一个参数(会出现在左边)。这样就不符合我们的要求了。所以在这儿可以用友元函数来实现输入输出运算符的重载。

注意:友元函数会破坏代码的封装性,所以,在很多场合下并不推荐使用友元函数。

static修饰的类的静态成员

特点:在类里面,用static修饰的成员,成为类的静态成员。

类的静态成员,被类的所有对象共享。

class Date
{
	public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		++_count;
	}
	static void GetCount()
	{
		//在static函数内部,不可以调用非static成员,因为没有this指针,无法进行访问
		cout<<Date::_count <<endl; //因为用static修饰的类的成员函数,没有this指针,所以只能通过类来访问
	}
private:
	int _year;
	int _month;
	int _day;
	static int _count;
};
int Date::_count = 0;

敲黑板划重点啦:在类的静态成员函数中不可以访问非静态成员函数。在静态成员函数中没有this指针。

                            而在类的非静态成员函数中可以访问类的静态成员函数。


猜你喜欢

转载自blog.csdn.net/yinghuhu333333/article/details/79581433