目录
一、内联函数
1.1内联函数概述
背景知识:
函数调用的实质是,将程序执行的顺序转移到被调用的函数所在
的内存地址,将函数执行完后,再返回到原来的地址继续往下执行,
因此需要保护现场并记忆执行的地址,还要恢复现场。即不断地有
函数入栈,会造成栈空间或栈内存的大量消耗。栈空间是指放置函
数内数据的内存空间,是有限的,假如频繁大量的使用就会造成因栈
空间不足所造成的程序出错问题。而且,函数调用需要消耗一定的
时间和空间,于是影响了效率。
内联函数概念:
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在
每一个调用处。
1.2如何实现
正常定义一个函数,然后在前面加上inline
inline int getmax(int x,int y){
retyrn x>y?x:y;
}
1.3内联函数适用场景
小函数(几行、几十行的)、大频率地调用 则适合内联;
大函数、稀少调用 则不适合内联;
递归函数不能内联;
inline只是一种请求,请求成功则皆大欢喜按照内联方式
若请求不成功则按照普通函数调用。
二、参数哑元
2.1概念
如果一个函数参数只有类型而没有参数名,则叫哑元。
2.1作用
①让参数列表匹配更加严格;
②保持函数的向前兼容;
如前人写的函数为void decode(int pkey);
而你很厉害,改进了这个函数,你用不到形参pkey了,
但是又为了保留以前的调用习惯而保留形参(当然你不用)
,则你可以写为void decode(int);
③区分函数
前++
operator++();//代表前++
后++
operator++(int);//代表后++
(以后在运算符重载再讲)
三、参数的默认值
3.1概念
如果一个函数的参数设置了默认值,则调用这个参数时可以
不传值,这时使用的值就是默认值。如果对这个参数传入值,
则传入的值会替代掉默认值。
3.2语法
①语法一
函数的默认值必须靠右,比如下面的语法可以:
void foo(int a,int b,int c = 0);
而这个就不可以:
void foo(int a = 1,int b,int c = 0);
②语法二
函数的声明和函数的实现分开时,默认值在声明时指定
3.3程序举例
#include <iostream>
using namespace std;
int getmax(int x = 100, int y =1);
int main(){
cout << getmax() << endl;
cout << getmax(2) << endl;
cout << getmax(1,101) << endl;
/*运行结果:
100
2
101
*/
}
int getmax(int x, int y){
return x>y?x:y;
}
四、c++ 动态内存分配new()
背景知识:malloc的含义是“给我一个大小为size的连续内存”,
而calloc是“给我n个大小为size的内存”。
①new(相当于c中的malloc()) 、 delete
在堆中动态的申请内存。
类型 *指针名 = new 类型;
如:int *pi = new int;//说明你申请int即4字节空间
如:int *pi2 = new int(100);
/*意为:申请int型4字节空间,并将数值100赋值到该空间*/
补充,无论是new,还是malloc,都是申请连续的内存空间,也就
是说,你同样也可以把一个结构体放到这个内存空间。如:
struct Data{int a;double b};
Data *p = new Data;
②new[](相当于c中的calloc()) delete[]
即申请 多个 对象的空间
类型 *parr = new 类型[n];
程序举例:
#include <iostream>
using namespace std;
int main(){
/*申请5个整数的堆空间*/
int *parr = new int[5];
/*给这5个整数赋值,并输出*/
for(int i = 0; i<5; i++){
cin >> *(parr+i);
/*另一种方法:写为parr[i]也行!*/
}
for(int i = 0; i<5; i++){
cout << *(parr+i);
}
/*本程序注意,为了给5个数赋值,不可以写为*parr++,
因为:
和上面的两种方法不同,你这样你就操作指针了,你直接把指针
的位置给动了。
一个经验是:我们迫不得已操作指针时,为了避免破坏原来指针的
位置,通常我们把指针赋值给另一个变量,我们去操作那个变量*/
/*释放这块内存*/
delete[] parr;
}
③一个知识点
我们知道new申请的内存在堆中,我们可不可以把这个堆中的空间
挪到栈中?答案是可以的。
char data[100];//在栈中申请100个字节内存
int *pa = new (data) int[25];//在data中申请100个字节。
并且你要知道的是:pa和data这两个地址是一样的:
cout << pa << endl;
cout << (void*)data << endl;/*data是指针,但是c++容易
把它当成字符串*/
五、c++中的引用
5.1概念
引用就是别名,如:
9527 华安 唐伯虎 唐寅
机器猫 叮当 哆啦A梦
5.2语法
int var_x = 9527;
//我们给9527起个别名
/*rvar_x就是变量var_x的别名。
定义别名必须初始化;
引用一旦初始化,则在整个程序运行中为这个变量var_x服务*/
int& rvar_x = var_x;
5.3程序举例
#include <iostream>
using namespace std;
int main(){
int var_x = 9527;
int& rvar_x = var_x;//定义一个引用,并初始化引用var_x
cout << var_x << endl;//9527
cout << rvar_x << endl;//9527
rvar_x = 9526;
cout << var_x << endl;//9526
/*当然我们可以对var_x起多个别名*/
int& rrvar_x = var_x;
/*引用一旦初始化,则引用的对象终生不能改变*/
/* 显然,下面这样写是错的:
int y = 1000;
int& rvar = y;
*/
/*但是这样写没问题:
(这只是赋值,不是引用)*/
int y = 1000;
rvar = y;
}
5.4引用的简单实现(原理)
背景知识:const修饰变量,功能是对变量声明为只读特性,
并保护变量值以防被修改。
具体实现:
类型 *const 指针名;
代码:
#include <iostream>
using namespace std;
int main(){
int var_t = 9577;
/*const修饰的指针pi可以改变指向,但不能通过pi修改值*/
int const *pi = &var_t;
int var_y = 100;
pi = &var_y;//改变pi指向
//*pi = 10;这是错的,不可以修改
/*好,看看引用的底层实现*/
int * const rpi = &var_t;
//rpi = &var_y;这是错的,不能改变rpi指向
*rpi = var_y;//但可以修改指针所指向的值
}
5.5引用的应用
①函数的参数
引用传递(c++) vs 值传递(c语言)
引用传递
减少拷贝
在函数内部修改函数外部的数据
写一个交换值的例子:
#include <iostream>
using namespace std;
void myswapa(int x, int y){
int temp = x;
x = y;
x = temp;
}
void myswapb(int *x, int *y){
int temp = x;
x = y;
x = temp;
}
void myswapc(int& x, int& y){
int temp = x;
x = y;
x = temp;
}
/*const可以防止函数内部对x修改;
增加函数兼容性,可以传const修饰的变量和字面量*/
void printVar(const int& x){
cout << "x=" << x << endl;
}
int main(){
int x = 20;
int y = 123;
/*方法一:失败(不可行)*/
myswapa(x,y);
/*方法二:成功*/
myswapb(&x, &y);
/*方法三:成功*/
myswapc(x, y);
/*总结: 方法一和方法二都是c用法,是值传递。方法一是失败的;
方法三用的引用,即对main()中的x、y起了别名为myswapc中的x、y
*/
printVar(x);
printVar(100);//printVar有const,因此可以打印常量
}
②函数的返回值
函数的返回值只能作为右值,不能作为左值。如:
int z = getmax(x,y);//把函数返回值赋给z,函数
在等号右侧。
如果希望函数的返回值作为左值,则可以使用引用类型。
看程序:
#include <iostream>
using namespace std;
int& getmax(int&x, int&y){
return x>y?x:y;
}
int main(){
int x = 20;
int y = 123;
int& z = getmax(x, y);
/*让getmax()作为左值*/
getmax(x,y) = 1000;//把1000赋给getmax()的返回值y,则z=y = 1000
cout << z << endl;//1000
cout << y << endl;//1000
}
5.5思考指针和引用的练习和区别?
六、c++中的四种类型转换运算符
6.1 static_cast<类型>(变量)
在某一个方向上可以做隐式类型转换。
程序举例:
#include <iostream>
using namespace std;
int main(){
int *pi = new int(100);
void *pv = pi;
//想要输出pv所指的值,就得把其转换为整型指针
int *ti = static_cast<int *>(pv);
cout << *ti << endl;
}
6.2 dynamic_cast<类型>(变量)
适合多态性的父子类之间
(本小节以后再讲)
6.3 const_cast<类型>(变量)
作用:去掉const修饰
程序举例:
#include <iostream>
using namespace std;
int main(){
const int x = 1000;
int *pi = const_cast<int*>(&x);/*我们知道,x是const修饰的,是不能改的
但是我们就想改。
你可以通过指针来改,但是前提是你得去掉x的const修饰*/
*pi = 99;
cout << x << endl;/*x=100。按理说x应该是99啊,但是
c++有个毛病,被const修饰过的x,c++编译器会把所有
的x当成100。
理一下思路:x被const修饰,不可更改。
但被const_cast<int*>(&x)之后去掉了const,按理说能更改了
但是c++编译器把所有的x当成100,怎么办?
答案是,把const int x = 1000;改为
volatile const int x = 1000;
这样一来x就等于99了。
*/
cout << *pi << endl;//99
}
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
6.4 reinterpret_cast<类型>(变量)
重新解释内存,最接近C的 强制类型转换。
可以把整数变成指针
也可以把指针变成指针
七、c++之父给c程序员的建议
1、尽量少使用宏,可以使用枚举和const定义常量
用inline替代掉带参的宏
使用namespace避免命名冲突
2、变量随用随时定义,以保证变量的初始化
如:for(int i=0;i<5;i++){}
3、尽量避免使用c语言的强制类型转换,如果要进行类型转换,
尽量使用C++提供的四个转换运算符.
4、多用new delete少用malloc free
5、少使用c风格的字符串,多使用string类型
6、逐渐建立面向对象的编程思想。