上次介绍了:开启C++之旅(上):探索命名空间与函数特性(缺省参数和函数重载)
今天就接着进行c++入门的知识讲解
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。通过引用,你可以使用一个变量的多个名称来访问和修改它的值 定义形式: 类型& 引用变量名 = 引用实体(这里&就不是c中大家熟知的取地址了) 注意:引用类型必须和引用实体是同种类型的
int main()
{
int a = 10;
int& b = a;
cout << a << endl;
b = 1;
cout << a << endl;
return 0;
}
二者也是共用一块内存空间
int main()
{
int a = 0;
int& b = a;
int c = 10;
b = c;//那这个到底是 c变成d的别名?还是d赋值给c?
return 0;
}
因为引用不能改变指向,这个是赋值
int main()
{
const int a = 10;
int& ra = a;//这样编译器会报错
return 0;
}
实际上这样会扩大权限:本身用**const
**修饰后,不能改变**a
**的值了,但是如果引用后就能利用引用改变。这样扩大了权限
这时使用常引用
int main()
{
const int a = 10;
const int& ra = a;//这样才对,没有扩大权限
return 0;
}
int main()
{
int a = 10;
const int& ra = a;//这样也可以,不会报错
return 0;
}
权限缩小是没问题的
在 C++ 中,隐式类型转换、截断和强制类型转换都可能导致临时变量的创建,并且这些临时变量通常具有常量性质,那就需要用常引用
加了**
const
**就好了
在 C++ 中进行类型转换时,通常会创建一个临时变量来存储转换后的结果。这个临时变量是一个匿名对象,它存储了转换后的值,但并不会影响原始变量的值。这也是为什么对**
a
**进行类型转换后赋值,但是**a
**不发生变化
在函数中使用引用作为参数,可以让你直接操作传递给函数的变量,而不是对其进行复制。这样可以避免复制大型对象,提高效率,同时允许函数修改传递的变量值(可以简单理解为:我们把别名传了过来,当然能通过别名来改变本身)
void Swap(int& a, int& b)//交换两个整形
{
int temp = b;
b = a;
a = temp;
}
在 C++ 中,函数可以返回引用,以避免在返回函数结果时产生拷贝。直接返回传递的变量的引用,允许你对该变量进行操作。然而,使用引用作为返回值需要小心,确保引用所指向的变量在函数返回后仍然有效 使用引用作为返回值的语法是在函数声明或定义中将函数返回类型声明为引用类型。
但是注意
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;
}
ret
始终是函数调用时使用的空间里变量c
的别名出了函数作用域,返回对象(局部变量)就销毁了,不能用引用返回,否则结果是不确定 最好返回指向全局变量、静态变量、或动态分配内存的引用,确保引用在函数返回后仍然有效
int& Add(int a, int b)
{
static 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(3, 4) is :" << ret << endl;
return 0;
}
仍然有问题,因为
static
关键字用在局部变量上表示该变量在程序运行期间只初始化一次,后续再次调用时,不走那条语句了,直接**return c
**
int& Add(int a, int b)
{
static int c ;
c = a + b;//分开就行了
return c;
}
相信大家一开始都会抱有疑问,引用现在能做的指针不也都可以吗? 还有那个别名的底层是什么,怎么理解?
虽然在底层里,创建的引用变量实际是有空间的,可以通过汇编来观察引用是按照指针方式来实现的
但是
在语法上:我们只是给那个空间取了一个别名,没有开辟空间
int main()
{
char a = '1';
char& b = a;//如果开了空间,大小是4,没有就是1
cout << sizeof(b);
return 0;
}
其他区别:
在学习c时,我们认识了宏
优点: 1.增强代码的复用性 2.提高性能。 缺点: 1.不方便调试宏(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查
为了解决缺点,c++中采用:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率(用展开函数体来替代函数调用)
我们使用内联函数:
关于第三点:内联函数因为直接展开,也就不要地址查询(内联函数名不会进入符号表),我们之前经常在头文件里进行声明,一个源文件里面进行实现。现在在其他源文件里使用内联函数时不行的
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
auto就是来解决这个问题
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
int main()
{
auto a = 1;
auto b = 1.1;
auto c = 'c';//但这些都不是主要的使用场景
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
用**
auto
**声明指针类型时,用**auto
**和**auto*
**没有任何区别,但用**auto
**声明引用类型时则必须加**&
**
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
之前我们写c的时候,在C++98中如果要遍历一个数组,可以按照以下方式进行:
void Test1()
{
int arr[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i] *= 2;
}
for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); p++)
{
cout << *p << endl;
}
}
现在我们可以这样:
void Test2()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)//用for(int& e:array)也可以
{
e *= 2;
}
for (auto e : array)
{
cout << e << " ";
}
}
C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围 基于范围的for循环会依次将容器中的元素赋值给迭代变量(通常命名为element)。在每次循环迭代中,迭代变量将会被赋值为容器中的下一个元素,直到遍历完整个容器
如果想要改变数组里,就使用引用
void Test2()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)
{
e *= 2;
cout << e << " ";
}
cout << endl;
for (auto e : array)
{
cout << e << " ";
}
}
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围
我们经常使用的NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,==NULL可能被定义为字面常量0,或者被定义为无类型指针(void)的常量==。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦* 所以我们使用nullptr来对指针进行初始化,来替代NULL,以免NULL定义为0时出现错误
注意:
好啦,c++入门的知识先到这里啦,下面就要开启面向对象的篇章了。感谢大家支持!!!