c++初篇02

目录

6.引用

6.1引用概念

 6.2引用特性

6.3常引用

6.4使用场景

1.做参数

2.做返回值

1)值返回和引用返回比较

2)不能使用引用返回的情况

3)引用返回相对于值返回的优点

6.5引用和指针的区别

7.内联函数

引入:

概念:

相较于宏的优点:

 特性:

8.auto关键字(c++11)

8.1 auto与typedef

8.2auto的使用细则

8.3auto不能使用的场景

9.基于范围的for循环(c++11)

9.1范围for的语法

9.2范围for的使用条件

10.指针空值nullptr


6.引用

6.1引用概念

引用不是新定义一个变量,而是给已存在变量取一个别名,编译器不会给其开辟内存空间,它与其所引用的变量共用同一块内存空间。

类型&引用变量名(对象名)=引用实体

 6.2引用特性

1.引用在定义时必须初始化

如下便是错误使用

int &b;

2.一个变量可以有多个引用

3.引用只能引用一个实体,即指向不可变,这也是c++中指针不可被替代的重要原因,java中引用指向可变,便无指针。c++中引用和指针并存。

6.3常引用

临时变量具有常性,发生类型转换时,会产生一个临时变量

6.4使用场景

1.做参数

例:

void Swap(int&left,int &right)
{
int temp=left;
left=right;
right=temp;
}

再如

#include <time.h>
struct A { int a[10000]; };

void TestFunc1(A aa) {}

void TestFunc2(A& aa) {}

void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

当参数大时,引用做参数可节省时间,不过同样此处用指针时也可省去时间。

2.做返回值

1)值返回和引用返回比较

在C++中,函数可以通过值返回或引用返回来返回值。

···当使用值返回时,函数将计算结果存储在临时变量中,并将其返回给调用方。在以下示例中,函数返回整数值:

int add(int x, int y) {
  int result = x + y;
  return result;
}

int z = add(3, 5); // z = 8

···当使用引用返回时,函数会返回对变量的引用,这意味着调用方可以修改该变量。在以下示例中,函数返回对int变量的引用:

int& get(int& x) {
  return x;
}

int a = 5;
int& b = get(a);

b = 10; // a = 10

这里函数get()返回对int变量的引用,调用方可以使用该引用来修改变量。在上面的代码中,变量a被传递给get()函数,然后将返回的引用赋值给变量b。此时,b指向变量a,因此对b的任何更改都将影响a

2)不能使用引用返回的情况

引用返回有以下情况不能使用:

  1. 函数返回的对象是局部变量:当函数返回一个对局部变量的引用时,当函数执行完毕后该对象将被销毁,这意味着返回的引用将指向一个不存在的对象,从而导致未定义行为。如:

    int& Add(int a, int b)
    {
    	int c = a + b;
    	return c;
    }
    
    int main()
    {
    	int& ret = Add(1, 2);
    	cout << "Add(1, 2) is :" << ret << endl;
    
    	Add(3, 4);
    
    	cout << "Add(1, 2) is :" << ret << endl;
    	return 0;
    }
    //出了函数作用域,返回对象就销毁了,不能使用引用返回,否则结果是不确定
  2. 函数返回的对象是静态变量:当函数返回一个对静态变量的引用时,该对象将始终存在于程序的生命周期中,因此返回的引用是有效的。但是,这可能会导致多线程问题,在多线程环境中,静态变量可能会被多个线程共享,因此对该变量的引用可能会出现竞争条件

  3. 函数返回的对象是临时变量:当函数返回一个对临时变量的引用时,该对象将在函数执行完毕后被销毁,从而导致返回的引用指向不存在的对象

  4. 函数返回的对象是常量:当函数返回一个对常量的引用时,该引用不能用于修改对象,因此使用引用返回没有任何好处。

综上所述,当函数返回一个对象的引用时,必须确保该对象的生命周期大于或等于返回的引用的生命周期。

一个静态变量只能初始化一次

3)引用返回相对于值返回的优点

引用返回相比值返回有以下优点:

     1.引用返回不会创建副本当使用值返回时,函数将返回值的副本,对于较大的对象,这可能会导致性能影响。而使用引用返回时,函数将返回对原始对象的引用,因此不会创建副本,因此更加高效。

    例如:

// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }

void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();

	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();

	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	//TestRefAndValue();
	TestReturnByRefOrValue();

	return 0;
}

     2.引用返回可以修改对象:当使用值返回时,返回的值是一个副本,对该副本的修改不会影响原始对象。而使用引用返回时,将返回对原始对象的引用,因此可以修改原始对象。

例如:

typedef struct SeqList
{
	int a[100];
	int size;
}SL;

void SLModify(SL* ps, int pos, int x)
{
	//...
	assert(ps);
	assert(pos < ps->size);
	ps->a[pos] = x;
}

// 引用做返回值:可以修改返回对象
int& SLat(SL* ps, int pos)
{
	assert(ps);
	assert(pos < ps->size);
	
	return ps->a[pos];
}

int main()
{
	SL s;
	//...

	SLat(&s, 3) = 10;

	// 每个位置的值++
	for (size_t i = 0; i < s.size; i++)
	{
		SLat(&s, i)++;
	}

	return 0;
}

     3.引用返回支持连续赋值:使用引用返回时,可以连续赋值。例如:

int a, b, c;
int& ra = a;
int& rb = b;
int& rc = c;

ra = rb = rc = 0;

在上面的例子中,rarbrc都是对变量abc的引用。因此可以使用连续赋值将它们都设置为0。

总的来说,引用返回可以提高代码的效率和可读性,并且支持一些方便的编程实践,如函数链式调用和赋值。

6.5引用和指针的区别

语法概念上:引用是一个别名,没有独立空间,和引用实体共用同一块空间。

底层实现上:实际上是有空间的,因为引用是按照指针来实现的。汇编代码相同

引用和指针的不同点:

1. 概念:引用在概念上定义一个变量的别名,指针存储一个变量地址。

2. 定义: 引用在定义时必须初始化,指针没有要求。

3. 指向:引用指向不可变,引用在初始化引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。

4. 空:没有NUll引用,但有NUll空指针

5. 所占空间大小:在sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数

6. 自加意义不同:引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7. 有多级指针,但没有多级引用

8. 访问实体不同:指针需要显式解引用,引用编译器自己处理。

9. 安全性:引用比指针使用起来相对更安全。

7.内联函数

引入:

c中的宏优点:是不建立栈帧;缺点:1、容易出错,语法细节多 2、不能调试 3、没有类型安全的检查。c++内联函数可作出改进。

概念:

以inline修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,提升程序运行的效率。

相较于宏的优点:

内联函数和宏函数都可以用来提高程序的运行效率,在一些情况下内联函数比宏函数具有一些优点。

  1. 类型安全:内联函数对于参数类型的要求比较严格,在函数定义时可以进行类型检查,从而避免了一些隐式类型转换带来的问题。而宏函数对于参数类型没有任何限制,容易引起类型错误。

  2. 调试方便:内联函数对于调试和跟踪程序的执行过程比宏函数更方便。内联函数在被调用时,可以设置断点进行调试,而宏函数的代码是直接展开的,不容易进行调试和跟踪。

  3. 可读性和可维护性:内联函数相对于宏函数更加易读、易维护和易于理解,因为内联函数的代码可以直接看到,不会像宏函数一样需要展开后才能看到代码。内联函数的代码结构也更加清晰,不容易出现嵌套和逻辑不清等问题。

  4. 函数特性:内联函数是真正的函数,具有函数的所有特性,例如参数的传递、返回值等。而宏函数只是一段代码的替换,不具备函数的特性,例如参数的传递、返回值等。

总体来说,内联函数相对于宏函数更加稳定、安全,易维护和易于理解。但是,在一些特殊的情况下,宏函数可能会比内联函数更加有效和灵活,例如在对于进行简单的数值计算时。因此,在实际开发中,需要根据具体情况选择合适的函数实现方式。

 特性:

1.inline是一种以空间换时间的做法。优点:少了调用开销,提高程序运行效率。缺点:可能会是目标文件变大,这也是为何不全部使用inline的原因。

2.inline相对编译器而言只是一个建议,不同编译器对于inline实现机制可能不同。一般建议:将函数规模较小(即函数不是很长,具体取决于编译器内部实现,10行左右),不是递归,且频繁调用的函数采用inline修饰,否则编译器忽略inline特性。

3.inline不建议声明和定义分离,分离会导致链接错误。简单理解,inline声明与定义若分开,在编译时它不会像普通函数一样在编译时找到定义,因为它是直接展开到所用地,也就是不产生像普通函数一样的函数地址,这一点在反汇编语言中可看到不同。那么分离后便导致链接错误。

// func.h
inline int add(int a, int b) {
  return a + b;
}
 

故一般在头文件中将声明定义放一块(这一点与普通函数不同,普通函数这样做可能会导致同一个定义出现在多个编译单元中,出现链接错误)

8.auto关键字(c++11)

8.1 auto与typedef

typedef 和 auto 都是 C++ 中的类型别名。但是二者主要用途和使用方式有所不同。

typedef 是一种定义自定义类型别名的方式,它可以将一个已经存在的数据类型重命名为另一个名字,使得代码更加简洁易读。例如:

typedef int Integer;
Integer num = 10; // 等同于 int num = 10;
 

auto 是一种类型推导关键字。它可以在定义变量时省略变量类型的声明,编译器会根据变量初始化表达式自动推导出变量的类型。例如:

auto num = 10; // 推导出 num 的类型为 int
 

auto 的优点在于:

  1. 可以使代码更加简洁,特别是对于较长的类型名或模板类型参数。
  2. 有助于降低代码重构的难度,因为只需要改变初始化表达式的类型即可自动更新变量的类型,而无需手动更改所有的变量声明。
  3. 可以避免类型定义时重复书写类型名,减少出错的可能性,特别是当类型名较长或较复杂时。

注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

8.2auto的使用细则

1.auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但是用auto声明引用类型时则必须加&

原因:这是因为 C++11 标准中规定,使用 auto 推导变量类型时,会忽略初始化表达式的顶层 const 和引用类型,只推导出变量的值类型,因此需要手动加上引用符号。

例:

int a = 10;
int& b = a;
auto c = b;  // 编译器推导出 c 的类型为 int,而不是 int&
 

编译器会忽略初始化表达式 b 中的引用符号,推导出 c 的类型为 int。如果需要声明一个引用类型的变量来引用变量 b,则需要加上引用符号:

int a = 10;
int& b = a;
auto& c = b;  // 声明一个引用类型的变量 c,引用变量 b
 

2.在同一行中定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际上只对第一个类型进行推导,然后用推导出来的类型定义其他变量

void TestAuto()
{
auto a = 1,b =2;
auto c = 3,d =4.0;//该行代码会编译失败,因为c和d的初始化表达式类型不同

}

8.3auto不能使用的场景

1.auto不能做函数的参数

2.auto不能直接用来声明数组

9.基于范围的for循环(c++11)

9.1范围for的语法

c++98中如果要遍历一个数组,可以按照以下方式进行

 void TestFor()
{
int array[]={1,2,3,4,5}
for(int i=0;i<sizeof(array)/sizeof(array[0]);++i)
array[i]*=3;
for(int*p=array;p<array+sizeof(array)/sizeof(array[0]);++p)
cout<<*p<<endl;
}

对于有范围的集合而言,由程序员本身来说明范围是多余,且有时候还容易出错,c++11中引入基于范围的for循环,for循环后的括号由':'分成两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
     e *= 2;
for(auto e : array)
     cout << e << " ";
return 0;
}

9.2范围for的使用条件

​​​​​​​1.范围for迭代的范围必须是确定的

对于数组而言,就是数组的第一个元素和最后一个元素的范围;

对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

例:for的范围不确定,错误

void TestFor(int array[])//其实这里传过来的本质上是指针,
{
for(auto& e: array)
cout<<e<<endl;
}

2.迭代的对象实现++和--的操作————>使用引用,e++(e--)

10.指针空值nullptr

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

故引入nullptr表示指针空值

注意:

1.nullptr是c++11作为新关键字引入的。2.在c++11中,sizeof(nullptr)与sizeof((void*)0)所占字节相同,即等价。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值