概述
内联函数
内联函数是c++中为了提高程序运行速度所做的一个改进
常规函数和内联函数的主要的区别不在于编写方式,而在于c++你一起如何将其组合在程序中去
常规函数在运行过程中,执行到程序调用指令的时候,程序将在函数调用之后立即存储该指令的内存地址,将函数参数复制到堆栈
跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的地址指令处。
来回跳跃并记录跳跃位置意味着以前使用函数的时候需要一定的开销
c++内联函数提供了另一种选择,编译器将使用相应的函数代码替换函数调用,对于内联代码,函数无需跳到另一个位置处执行代码再跳回来
所以:内联函数运行速度比常规函数快,但是代价是需要占用更多的内存。
下面两种措施是使用内联函数的方法
在函数声明前加上关键字inline;
在函数定以前加上个关键字inline;
但是当程序员调用内联函数的时候,编译器也不一定会满足要求
如果函数过大或者是调用了自己(内联函数不允许递归)则其不能成为内联函数
调用内联函数的时候,系统将函数中的代码插入调用该函数的语句的地方,实参代替形参,程序运行的时候不再进行函数调用
作用:消除函数调用的系统开销,提高运行速度
内联代码中不能由复杂语句 比如if for 等
如果代码规模较小 且频繁使用 大大提高程序运行速度
如果加上for等复杂的语句 那么会使得程序的总代码量增大,消耗更多的内存空间,开销要比函数调用的开销大 得不偿失
内联是以代码膨胀为代价 仅仅省去了函数调用的开销,从而提高函数的执行效率 所以使用内联的时候要注意时机
引用变量
引用就是已定义的变量的别名(另一个名称)
创建引用变量
我们知道c和c++中 & 是来表示变量的地址的,c++给 & 赋予了一个新的含义用来声明引用
int rats;
int & rodents=rats;
其中&不是地址运算符,而是类型标识符的一部分,int&指向的是int的引用
上述rats和rodents允许将两者互换,他们指向相同的内存单元和值
下面给出一段代码方便大家理解
int main() {
int rats = 100;
int& rodents = rats;
cout << "rats:" << rats << endl;
cout << "rodents:" << rodents << endl;
rats++;
cout << "rats:" << rats << endl;
cout << "rodents:" << rodents << endl;
cout << "rats address:" << &rats << endl;
cout << "rodents address:" << &rodents << endl;
return 0;
}
可以看到我们运行的结果rats和rodents的值还有地址都是相同的
下面我们再次执行一段代码观察结果
int main() {
int rats = 100;
int& rodents = rats;
cout << "rats:" << rats << endl;
cout << "rodents:" << rodents << endl;
rats++;
cout << "rats:" << rats << endl;
cout << "rodents:" << rodents << endl;
cout << "rats address:" << &rats << endl;
cout << "rodents address:" << &rodents << endl;
cout << "--------------------------------------------------------------" << endl;
int bunnies = 50;
rodents = bunnies;
cout << "rats:" << rats << endl;
cout << "rodents:" << rodents << endl;
cout << "bunnies:" << bunnies<<endl;
cout << "rats address:" << &rats << endl;
cout << "rodents address:" << &rodents << endl;
cout << "bunnies address:" << &bunnies << endl;
return 0;
}
最初的时候,rodents的引用是rats,随后我们将rodents作为bunnies的引用
但是我们发现rats的值也变成了50
同时rats和rodents的地址还是相同的,和bunnies是不同的
所以我们可以通过初始化声明来设置引用,但是不能通过赋值来设置
将引用用作函数参数
引用常被作为函数参数,使得函数中的变量名成为调用程序中的变量的别名
这种传递参数的方法叫做按引用传递,按引用传递允许被调用的函数能够访问调用函数中的变量
我们简单写一个双指针的数组的翻转函数
分别使用按值传递和按引用传递观察结果
vector<int> reverse(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
swap(nums[left], nums[right]);
left++;
right--;
}
return nums;
}
vector<int> reverse2(vector<int> nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
swap(nums[left], nums[right]);
left++;
right--;
}
return nums;
}
int main() {
vector<int> a = { 1,2,3,4,5 };
vector<int> t1= reverse(a);
vector<int> t2 = reverse2(a);
cout << "引用传递:" << endl;
for (auto t : t1) {
cout << t<<" ";
}
cout << endl;
cout << "值传递:" << endl;
for (auto t : t2) {
cout << t << " ";
}
return 0;
}
可以很直观的看到两个的区别
我们可以发现两个函数的唯一区别就是声明的时候的唯一一点点区别
但是在引用传递中nums是主函数a的别名
在值传递中nums光是复制了a的值不会对a产生影响
这就是引用用作函数参数的内容
引用的属性和特别之处
在使用引用参数的时候,我们需要了解一些特点
先看一个例子
double cube(double a) {
a *= a * a;
return a;
}
double refcube(double &ra) {
ra *= ra * ra;
return ra;
}
int main() {
double x = 3;
cout << cube(x) << endl;
cout << x<<endl;
cout << refcube(x) << endl;
cout<< x;
return 0;
}
我们可以发现按值传递和按引用传递的不同点
按引用传递返回的的x也会发生改变
我们把下面的代码输入到编译器会报错
double refcube(const double &ra) {
ra *= ra * ra;
return ra;
}
因为按引用传递的性质
里边的参数的使用是由严格限制的
临时变量 引用参数和const
如果实参和引用参数比匹配的话,c++将会产生临时变量,仅当参数为const引用的时候,c++才允许这样
实参的类型正确,但不是左值
实参的类型不正确,但可以转换为正确的类型
这里就说下左值
可被引用的数据对象,例如变量,数据元素,结构成员,引用和解除与引用的指针
所以我们将引用参数声明为常量数据的引用的理由有三个
const可以避免无意修改数据
const是函数能够处理const和非const实参
const引用时函数能够正确生成并使用临时变量
虚函数
定义
基类中被virtual关键字声明,派生类中重新定义的函数
**作用:**允许在派生类中重新定义与基类重名的函数,并可通过基类指针和引用来访问基类和派生类的重名函数
使用虚函数可以获得良好的可扩展性,在一个设计比较好的面相对象程序中,大多数函数都是与基类接口进行通信
简述C++虚函数作用及底层实现原理
虚函数使用虚函数表和虚函数指针来实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地址
如果子类重写了父类的虚函数,则在对应的虚函数表中把水印的虚函数替换为子类的函数的地址
虚函数表指针存于每个对象中,它指向对象所在类的虚函数表的地址,
多继承的环境下,会存在多个虚函数表指针,分别指向不同基类的虚函数表
虚函数表示每个(有虚函数)类对应一个,虚函数表指针是每个对象对应一个
虚函数表中只能存放虚函数,不能存放普通函数
如果一个函数不是虚函数,对他的调用在编译时就会确定
如果是虚函数,对他的调用在运行时确定
虚函数的函数入口是动态绑定的,运行时,程序根据基类指针指向的实际对象
来调用该对象对应版本的函数(用该对象的虚函数表指针找到虚函数表,进而调用不同的函数)
为什么要虚析构函数
在存在类继承并且析构函数中需要析构某些资源的时候,析构函数需要的是虚函数
否则如果使用父类指针指向子类对象,在delete的时候,只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄漏
简言之:如果基类析构函数非虚 派生类的析构函数不会被调用 造成内存泄漏
一个对象访问普通成员函数和虚函数哪个更快
普通成员函数更快,普通成员函数地址在编译阶段就确认
而虚函数先要在虚函数表中寻找到虚函数所在地址 比普通函数要慢
析构函数一定是虚函数吗?
不一定 如果没有子类,没必要使用虚函数 虚函数效率稍低一点相对于普通函数
因为普通函数在编译阶段 其地址就会被确认,但是虚函数先要在虚函数表中寻找虚函数所在地址
内联函数、构造函数、静态成员函数可以是虚函数吗?
都不可以
**内联函数:**需要在编译阶段展开,而虚函数是运行时绑定的,编译时无法展开 产生矛盾
**构造函数:**在进行构造函数的时候还没父子类,父类只会调用父类的构造函数,子类调用子类的构造函数,不存在动态绑定的概念
**静态成员函数:**以类为单位的函数,与具体对象无关,虚函数是和对象动态绑定的
构造函数可以调用虚函数吗?
可以 但是没有意义,起不到动态绑定的效果,父类构造函数中的调用仍然是父类版本的函数
子类也是子类版本的构造函数
简述c++中虚函数的作用以及底层实现原理?
虚继承用于解决多继承条件下菱形继承的问题,通过虚函数指针实现
各个对象中只保存一份父类的对象,多继承时通过虚基类指针引用该公共对象,从而避免菱形继承的二义性问题
纯虚函数
没有函数体的函数
virtual void Print( ) = 0 ; //纯虚函数
包含纯虚函数的类叫做抽象类
抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
抽象类的指针和引用可以指向有抽象类派生出来的类的对象
抽象类的派生类如果没有重写纯虚函数,派生类仍然为一个纯虚函数
虚继承(解决菱形继承的问题)
首先介绍菱形继承
如果我们不使用虚继承的方法 就会出现二义性
class A {
public:
int a;
};
class B : public A {
public :
int b;
};
class C : public A {
public :
int c;
};
class D : public B, public C {
public:
int d;
};
int main() {
D d;
d.a = 5;
cout << d.a << endl;
return 0;
}
我们把上边代码放入vs中可以发现是编译不通过的 编译器会报错D::a不明确
然后我们用虚继承来解决 给B和C类的继承前面加上virtual关键字
class A {
public:
int a;
};
class B :virtual public A {
public :
int b;
};
class C :virtual public A {
public :
int c;
};
class D : public B, public C {
public:
int d;
};
int main() {
D d;
d.a = 5;
cout << d.a << endl;
return 0;
}
这样可以完美解决这个问题
所以虚继承可以完美的解决多父类对象重复成员只保留一份的问题
动态联编和静态联编
当基类的函数没有加上virtual(成为虚函数)之前
函数是处于静态联编的状态(程序编译之前链接)
效率高(编译阶段即可知道全部信息)
如果成为虚函数的话 就是动态联编(程序执行时链接)
更高的灵活性,程序可维护性,问题抽象性
class animal {
public:
virtual void speak() {//没加virtual是静态联编 加了virtual是动态联编
cout << "动物在叫" << endl;
}
};
void dospeak(animal& ani) {
ani.speak();
}
class cat :public animal {
void speak() {
cout << "小猫叫" << endl;
}
};
class dog :public animal {
void speak() {
cout << "小狗叫" << endl;
}
};
int main() {
cat c;
dospeak(c);
}
上边例子中如果不加virtual的话 输出动物在叫
加上virtual就是小猫叫
实现了面相对象的思想
友元类(函数)
友元是c++提供的一种破坏数据封装和数据隐藏的方式
通过将一个模块声明为另一个模块的友元,一个模块可以引用到另一个模块中的被隐藏的信息
可以使用友元函数和友元类
为了确保数据的完整性,以及数据封装与隐藏的原则,建议不要使用或者少使用友元
友元函数
在类的声明中有friend来修饰说明 可以访问到private和protected成员
增强代码灵活性,使得程序员可以在封装和快速性方面进行合理的选择
访问对象中的成员必须通过对象名
class Point { //Point类声明
public: //外部接口
Point(int x = 0, int y = 0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(Point& a, Point& b);//声明友元
private: //私有数据成员
int x, y;
};
float dist(Point& a, Point& b) {
double x = a.x - b.x;//由于友元的原因 所以不会报错
double y = a.y - b.y;//由于友元的原因 所以不会报错 因为访问了private
return static_cast<float>(sqrt(x * x + y * y));
}
友元类
若一个类为拎另一个类的友元,则该类的所有成员都能访问对方类的所有私有成员
class A {
friend class B;
public:
void display() {
cout << x << endl;
}
private:
int x;
};
class B {
public:
void set(int i);
void display();
private:
A a;
};
void B::set(int i) {
a.x = i;//由于友元类的使用 使得私有变量x可以访问
}
void B::display() {
a.display();
};
友元关系是单向的
最后
以上就是淡定砖头为你收集整理的c++函数探幽(内联函数,引用变量,多态(虚函数),友元类(函数))的全部内容,希望文章能够帮你解决c++函数探幽(内联函数,引用变量,多态(虚函数),友元类(函数))所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复