c++基础知识3以及类和对象初识
指针和引用的区别
内联函数的概念
auto关键字(c++11)
指针控制nullptr(c++11)
类和对象初识
嗨喽,大家好呀,今天给大家带来的是我们c++基础知识最后一弹,以及初识我们的类和对象,下面让我们开始学习吧!
1.指针和引用的区别
上一节我们认识了引用,并且介绍了引用最基本的用法,引用作为函数的别名,可以作为函数的形参,与实参共用一块空间,直接对实参进行改变,相较于指针和传值是方便了不少,今天让我们更深入了解一下引用与指针的区别吧
下面我们首先要将语法层面和底层这两个分开来看待,就比如说老婆饼里没有老婆,而鱼香肉丝里没有鱼,在语法概念上来说,引用相当于是一个别名,和引用的实体共用一块空间
int a = 5;
int& b = a;
cout << "&a = " << &a << endl;
cout << "&b = " << &b << endl;
return 0;
从图中我们可以很明显的看出a和他的别名b公用的是同一块空间,但是我们需要从底层来认识一下引用,下面我们将引用和指针的汇编语言展示出来,大家看能不能从中发现什么
我们定义了一个指针p来保存a的地址,从汇编语言我们可以看出,引用和指针是一模一样的,都是在内存中开了一块空间,将a的地址存进去
那么指针和引用在底层上是相同的,这两个有什么区别呢?
指针和引用的区别
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以指向别的同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节(在32位下是4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
2.内联函数
概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
内联函数实际上在编译时并不进行传统的函数调用。它的工作机制是这样的:
1.内联替换:当编译器遇到内联函数的调用时,它不会按照常规方式生成函数调用指令。相反,编译器会将内联函数的代码直接插入到调用内联函数的地方,替换掉原来的函数调用。这就像是手动将函数体复制粘贴到调用它的位置一样。
2.消除调用开销:由于内联替换,内联函数避免了传统函数调用所带来的开销,如参数传递、栈帧的创建和销毁、以及函数返回时的跳转等。这使得程序在执行时不需要为内联函数建立栈帧,因此也没有栈帧的创建和销毁过程。
3.优化执行效率:通过消除函数调用的开销,内联函数可以显著提高程序的执行效率。然而,这也需要权衡,因为内联函数可能导致代码膨胀,即程序的二进制大小可能会增加,这可能会影响程序的加载时间和内存占用。
但是我们需要注意:
1.当内联函数被插入到调用点时,它内部的局部变量仍然需要存储空间。这些局部变量并不是通过创建栈帧来保存的,而是直接在当前调用函数的栈帧中分配空间。换句话说,内联函数的局部变量成为了包含内联函数调用的那个函数的局部变量的一部分。
2.不是生成独立的函数指令,它的指令和变量都被整合到了调用它的函数的指令和变量中。这种整合是在编译时完成的,而不是在运行时。
下面我们给出一个简单的内联函数调用的例子
inline int add(int& x, int& y)
{
return x + y;
}
int main()
{
int a = 3,b = 5;
int tmp = add(a, b);
cout << "tmp = " << tmp<<endl ;
return 0;
}
tmp = 8;
但内联函数也有一些缺点,比如调用10000次这个函数,这个函数生成的函数指令有100行
展开10000*100
不展开10000+100
可以很明显看出如果函数指令过大时,内联函数反而会使得整个指令膨胀,可执行程序变大
,所以用inline关键字建议编译器将函数内联,但编译器并不一定会接受这个建议。编译器会根据函数的定义、大小、调用频率等因素来决定是否将函数内联。编译器也会进行其他优化,可能会忽略程序员的内联建议,甚至可能会内联没有inline关键字的函数。
3. auto关键字(c++11)
下面我们先来简单介绍一下c++11引入的一个关键字
int main()
{
auto e = 0;
auto x = 1.1;
return 0;
}
我们可以用auto来初始化我们的变量,并且运算符右边会自动推导类型
1.但是我们要注意当auto* = 时,我们的右边必须输入一个指针
2.并且我们在同一行定义多个变量时,必须要是相同类型
3.auto不能被用来做函数的参数
4.auto不能用来声明数组
接着我们来介绍一个特别好用的遍历数组的方法:范围for循环
int main()
{
int array[] = { 2,4,6,8,10 };
for (auto e : array)//遍历数组中的元素
{
cout << e << "" << endl;
}
cout << endl;
for (auto& e : array)//将数组中的元素除二后=再遍历
{
e /= 2;
}
for (auto e : array)
{
cout << e << ""<<endl ;
}
cout << endl;
}
简单说明一下:auto e:array会自动取数组array中,赋值给e,并且会自动++判断结束
4.指针控制nullptr(c++11)
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nulptr是C++11作为新关键字引入
2.在C++11中,sizeof(nullptr)与 sizeof((void*)0)所占的字节数相同。
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
5.类和对象初识
下面终于来到了我们c++的精华,类和对象,下面我们就简单介绍一下类和对象
c++是一门面向过程与面向对象相结合的编程语言,并且c++完全兼容c,下面我们从struct来引入
1.C++兼容C中struct的用法
2.1个类 实例化 N个对象
3.C++升级struct升级成了类
// 1、类里面可以定义函数
// 2、struct名称就可以代表类型
typedef struct ListNodec
{
struct ListNodec* next;
int val;
}ListNodec;
struct ListNodecpp
{
ListNodecpp* next;
int val;
};
struct Stack
{
//成员函数
void Init(int n = 4)//缺省参数
{
array = (int*)malloc(sizeof(int) * n);
if (array == nullptr)
{
perror("malloc failed");
return;
}
top = 0;
capacity = n;
}
//成员变量
int* array;
int capacity;
int top;
};
int main()
{
struct Stack st1;
st1.Init(100);
Stack st2;
st2.Init();
ListNodec node1;
ListNodecpp node2;
return 0;
}
但是相比较struct定义类,c++更青睐于用class来定义类,讲解class前我们先引入类的访问限定符及封装的概念
下面给出几点说明
1.public修饰的成员在类外可以直接被访问
2.protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4.如果后面没有访问限定符,作用域就到}即类结束。
5.class的默认访问权限为private,struct为public(因为struct要兼容c)
class stack
{
public:
void Init(int n = 4)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
void Push(int x)
{
//扩容
array[top++] = x;
}
int Top()
{
assert(top > 0);
return array[top - 1];
}
private:
//成员变量
int* array;
int capacity;
int top;
};
下面我们简单提一下c++采用封装和类的好处
在c阶段,我们不免会有人写出cout<< st2.array[st2.top]<< endl;这样的代码,这是非常不合理的,因为top的初始化值并不知道,如果我进行封装,分成公有和私有,你想获得最后一个元素,只能运用类中给出的top接口,这对于规范程序员的素养有相当大的作用
好的,学习的时光总是美好而短暂的,期待我们的下一次相遇,希望大家觉得本篇博客有用的,来上一波收藏和点赞支持一下,后续会推出更多优质好文,大家再见!祝大家过一个充实的假期