.
个人主页: 晓风飞
专栏: 数据结构|Linux|C语言
路漫漫其修远兮,吾将上下而求索
文章目录
- C++输入&输出
- [cout 和cin](#cout 和cin)
- 缺省参数
- 函数重载
- [C++支持函数重载的原理--名字修饰(name Mangling)](#C++支持函数重载的原理--名字修饰(name Mangling))
- 引用
-
- 引用概念
- 引用特性
- 引用的作用
-
- 1.作为参数(输出型参数)
- [2. 做返回值(减少拷贝,提高效率)](#2. 做返回值(减少拷贝,提高效率))
- 2.对象比较大,减少拷贝,提高效率
- 指针和引用的区别
- 指针和引用的区别:
- 底层:
- 内联函数
C++输入&输出
cout 和cin
<<
这里的c意思是console,把数据
out
到console(控制台)中去,而最后面的endl
其实等价与\n
,就是换行
>>
同样的道理cin,把数据in到console(控制台),也就是输入数据到控制台中。
1.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
iostream >头文件中。
3. <<是流插入运算符,>>是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型。
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
全缺省
c
#include<iostream>
using namespace::std;
void Func(int a = 10 , int b = 20 , int c =30)
{
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "c:" << c << endl << endl;
}
int main()
{
// 没有传参时,使用参数的默认值
// 传参时,使用指定的实参
Func(1,2,3);
Func(1,2);
Func(1);
Func();
return 0;
}
那么可不可以隔着一个数传参呢?答案是不能
半缺省
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
c
#include<iostream>
using namespace::std;
//半缺省参数从右往左依次给出
//半缺省参数不是缺少一半,而是有缺少就是半缺省
void Func(int a , int b = 20 , int c =30)
{
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "c:" << c << endl << endl;
}
int main()
{
Func(1,2,3);
Func(1,2);
Func(1);
return 0;
}
应用场景
假如我有一个栈,但是不知道要插入多少数据,目前栈的空间是固定的,怎么解决数据的容量问题?
c
struct stack
{
int* a;
int size;
int capacity;
};
void stackInit(stack* ps)
{
//容量固定
ps->a = (int*)malloc(sizeof(int) * 4);
}
void StackPush(stack* ps,int x)
{
}
int main()
{
//不知道要插入多少个数据
}
用半缺省参数就可以很好的解决这个问题
声明和定义分离的情况
在声明和定义分离的情况下,那么是在声明处缺省,还是在定义处缺省呢?
c
//stack.h头文件下的定义
void stackInit(struct stack* ps, int n = 4);
//stack.cpp下的声明
void stackInit(struct stack* ps, int n)
{
ps->a = (int*)malloc(sizeof(int) * n);
}
应该在头文件下的定义处缺省,因为在运行时,要包含的是头文件,程序在编译的时候会展开头文件,这时候就可以进行缺省调用。而且在声明处还可以判断语法是否正确
如果在定义处缺省,那么在第3个情况下就会出现参数太少的报错情况,达不到缺省。
如果声名与定义位置同时出现缺省,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。出现重定义报错
函数重载
c语言不允许同名函数
c++可以,要求,函数名可以相同,但是参数不同,构成函数重载 ,并且会对数据类型自动匹配。
1.参数的类型不同
2.参数的个数不同
3.参数的顺序不同(本质还是类型不同)
c语言不支持重载,链接时,直接用函数名去找地址,有同名函数,区分不开。
那么C++是怎么支持的呢?
C++支持函数重载的原理--名字修饰(name Mangling)
函数名修饰规则,名字中引入参数类型,各个编译器自己实现了一套
Linux编译器的命名规则
因为Linux的规则比较简单,我们先理解一下Linux编译器的规则
解释:如果是Add这样的前面就是_Z3
,f就是_Z1
,后面就都是加上函数名字
和数据类型的首字母
正是用类似这样的规则给函数修饰名字,只要参数不同,修饰出来的名字就不一样,就支持了重载。这样链接的时候用这样的名字,就可以找到对应的函数地址
引用
引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
c
int main()
{
int a = 0;
//引用,b就是a的别名
int& b = a;
cout << &a << endl;
cout << &b << endl;
return 0;
}
注意:引用类型必须和引用实体是同种类型的
引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
4.引用不能改变指向
引用的作用
1.作为参数(输出型参数)
c
//指针传参
void Swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
//引用传参
void Swap(int &a , int &b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 0, y = 1;
Swap(&x, &y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
Swap(x, y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
}
这里a相当于x的别名,y相当于b
c
typedef struct Node
{
struct Node* next;
struct Node* prev;
}LNode,*Pnode;
void PushBack(Pnode& phead, int x);
void PushBack(struct LNode** phead, int x);
void PushBack(struct LNode*& phead, int x);
int main()
{
Pnode plist = NULL;
return 0;
}
这里*pnode 相当于struct Node*
,Node相当于struct Node
2. 做返回值(减少拷贝,提高效率)
c
int& func()
{
int a = 0;
return a;
}
int main()
{
int ret = func();
return 0;
}
这段代码意味着返回a别名,但是由于栈帧销毁,会造成野引用,这里的值是不确定的,取决于编译器,以及是否清内存。
可以看到这里随便调用了一个函数就导致结果变化,因为
fx
和func
相同,空间重复使用,所以在原来销毁的a的位置创建了b,所以导致输出来的值又a的6,变成了b的1。
小结:
返回变量出了函数作用域,生命周期就到了要销毁(局部变量),不能引用返回
那么怎么使用引用返回呢?
c
int& func()
{
static int a = 6;
return a;
}
int main()
{
int &ret = func();
cout << ret << endl;
return 0;
}
这里加上一个static
就可以。
c
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
c
#include<iostream>
using namespace std;
#include<assert.h>
struct SeqList
{
//成员变量
int* a;
int size;
int cacpcity;
//成员函数
void Init()
{
a = (int*)malloc(sizeof(int) * 4);
size = 0;
cacpcity = 4;
}
void PushBack(int x)
{
//... 扩容
a[size++] = x;
}
//临时变量有常性
//读写返回变量
int& Get(int pos)
{
assert(pos >= 0);
assert(pos < size);
return a[pos];
}
};
int main()
{
SeqList s;
s.Init();
s.PushBack(1);
s.PushBack(2);
s.PushBack(3);
s.PushBack(4);
for (int i = 0; i < s.size; i++)
{
cout << s.Get(i) << "";
}
cout << endl;
for (int i = 0; i < s.size; i++)
{
if (s.Get(i) % 2 == 0)
{
s.Get(i) *= 2;
}
}
cout << endl;
for (int i = 0; i < s.size; i++)
{
cout << s.Get(i) << "";
}
}
2.对象比较大,减少拷贝,提高效率
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。这些效果指针也可以,但是引用效率更高
c
#include<iostream>
using namespace std;
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void main()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
指针和引用的区别
指针和引用的区别:
1.引用是别名,不需要开空间
语法:
2.引用必须初始化,指针可以初始化也可以不初始化
3,引用不能改变指向,指针可以
4.引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用
底层:
1.引用底层是用指针实现的
2.语法含义和底层实现是背离
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
从汇编层面上,没有引用,都是指针,引用编译后也转换成指针了。