引用
对于一个常量,想要将其进行引用,则使用普通的引用相当于权限扩大(常量为只读,但此处的引用参数为可读可写),C++编译器会报错. 例如:
const int a = 10;
int& ra = a;//权限放大,会报错
所以C++中存在这样的语法:
const引用
在引用前加上const即为const引用:
//此时a只读不可写
const int a = 10;
const int& ra = a;
此时引用变量名也为常量(只读)
常量引用可以直接引用常量:
const int& rc = 30;
常量引用计算式或类型转换
类似 int& rb = a*3; double d = 12.34; int& rd = d;这样的式,都会将结果保存到临时变量中,而C++规定临时变量具有常性,所以需要使用常量引用.
int a = 10;
const int& ra = 30;
// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
// int& rd = d;
const int& rd = d;
注意
//此时b可读可写
int b = 20;
const int& rb = b;//权限缩小,可以实现
b++;
rb++;//此处会报错
我们设置一个变量b,并使用常量引用创造rb,此时的rb为只读,但b为可读可写,这就造成了权限缩小,可以实现.;但此时我们对b进行++,会发现b仍会改变,且常量rb也会改变,这是因为我们创建的常量是rb,而不是b,所以我们不能对rb进行操作,但可以对b进行操作.
引用与指针的关系
在C++中,引用与指针相辅相成,虽有其类似之处,但各有其特点,不可替代.
• 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
• 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
• 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
• 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
• sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下
占4个字节,64位下是8byte)
• 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
内联(inline)
1.⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内
联函数就需要建⽴栈帧了,就可以提⾼效率。
2.C语言会在预处理时展开内联函数,而inline对于编译器来说只是建议,若函数递归次数过深或函数语句较多编译器将不会展开内联函数。
3.宏函数的实现是复杂且易错的,C++设计内联函数是为了替代宏函数的.这里我们举个例子:写一个相加函数
#define Add(x, y) ((a) + (b))
这里提出三个问题:
为什么不能加分号?
为什么要加⾥⾯的括号?
为什么要加外⾯的括号?
A.宏函数是展开,所以在宏函数中加分号,当用于if else语句时也会带上';', 编译器将会报错
B.这里举一个反例:
int ret = ADD(1, 2);
cout << ADD(1, 2) << endl;
cout << ADD(1, 2)*5 << endl;
在宏函数中此处语句会被替换成1 + 2 * 5不是我们想要的值.
C.这里举一个反例:
int x = 2, y = 6;
ADD(x & y, x | y); // -> (x&y+x|y)
return 0;
此处会先进行加法运算而不是先进行按位与和按位或运算,与我们预期相悖.
4.inline函数是不能存在于两个文件中的,所以对于inline函数,我们直接在头文件中实现即可.
在c语言中,我们已经学过一种空指针“NULL”,NULL实际是个宏函数:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
但在C++中,NULL可能被定义为常量0,所以C++引入一个专门表示空指针的关键字:
新的空指针(nullptr)
C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int
//x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错:error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
}
此处NULL被错误的读成了0,导致了错误;且nullptr只能转换为指针类型,而不能转换为整型.
类和对象
class Stack
{
}
类的使用
我们以class Stack为例,class为类的定义类的关键字,此处的Stack为类的名字(可以自定义),类可以像C语言中的结构体一样存储成员,但与C语言中的结构体不同的是,类可以存储函数(被称为类的方法或成员函数).例如:
class Stack
{
void Init(int n = 4)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
int a;
int b;
};//分号不要忘了加
其中的Init()即为类的方法,a和b即为成员.
并且定义在类中的函数默认是内联函数(inline).
访问限定符
• C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的⽤户使⽤。
• public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访
问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
• 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。
• class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
• ⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
例如:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
// 为了区分成员变量,⼀般习惯上成员变量
// 会加⼀个特殊标识,如_ 或者 m开头
int _year; // year_ m_year
int _month;
int _day;
};
相对于C语言,C++中的struct结构体也可以包含函数了,可以包含函数的结构体与类的区别就是类中不写访问限定符即为private作用域,而struct中不写访问限定符即为public作用域.
我们来调用一下public作用域中的函数:
int main()
{
Date d;//C++可以直接用类名(类名代表类型)
//并且兼容C的struct用法
d.Init(2024, 7, 9);
return 0;
}
而想要调用private中的成员,则需要使用:
类域(class)
• 类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。
• 类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全
局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知
道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
举个例子,我们想要调用Stack域中的函数Init, 就应该这样:
void Stack::Init(int n)
{
}
函数的声明与定义分离
函数的声明:
class Stack
{
public:
// 成员函数
void Init(int n = 4);
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
而我们如果想要对函数定义,则需要专门使用类域来定义函数.