本片文章主要是帮助入门c++,因此将会补充C语言语法的不足,C++是如何对C语言设计不合理进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等,为后续类和对象学习打基础。
文章目录
1.命名空间
在C/C++中,变量、函数和还有c++中的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,我们在大量协同时或者不经意间变量、函数和还有c++中的类命名可能会相同导致很多冲突。 针对这个问题C++就提出了namespace关键字来应对这种问题。命名空间的目的是对标识符的名称进行本地化,以此来避免命名冲突.
1.1namespace关键字
namespace为关键字,定义命名空间在namespace后加上命名空间的名字,然后用{},{}内几位命名空间的成员,命名空间中可以定义变量/函数/类型,当然成员也可以嵌套命名空间。c++允许有多个命名空间,多个命名空间的名字也可以相同,编译器会将名字相同的命名空间合并。
例如下图n1和n2命名空间的内容是一致的名字不同,n1和n1名字相同,成员不同,第二个n1也再次嵌套一个命名空间
namespace n1
{
double a;
int b;
char c;
struct x
{
int d;
};
int add(int a, int b)
{
return a + b;
}
}
namespace n2
{
double a;
int b;
char c;
struct x
{
int d;
};
int add(int a, int b)
{
return a + b;
}
}
namespace n1
{
double i;
int j;
char k;
struct a
{
int dd;
};
int cdd(int a, int b)
{
return a + b;
}
namespace n3
{
int m;
}
}
接下来讲解,命名空间的使用方法。
有三种:
1.命名空间名称+作用域限定符
我们以上面的三个命名空间为例
2.使用using将命名空间中某个成员引入
3.用using namespace命名空间名称 引入
2.C++输入/输出
在c语言中通过包头文件(#include<stdio.h>)来使用printf函数输出,scanf函数输入。c++中也是需要使用包含头文件****,展开std来使用cout标准输出对象(控制台)和cin标准输入对象(键盘)。早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此建议使用+std的方式。
#include<iostream>
using namespace std;
int main()
{
int b ;
cin>>b;
cout << b << endl;//c++内endl表示换行
cout<<"hello world"<<endl;//c++能识别变量类型
return 0;
}
建议:那是不是所有情况都可以直接using namespace std即可?在平时练习时我们为了方便可以直接使用,展开后标准库就暴露了,如果我们定义与库相同的类型对象函数等,就会存在冲突,因此建议在项目开发中使用,像std::cout这样使用时指定命名空间 +using std::cout展开常用的库对象/类型等方式。
3.缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
#include<iostream>
using namespace std;
void add(int a=10)//
{
cout << a << endl;
}
int main()
{
add();//有缺省参数时,不传参使用缺省值;
return 0;
}
下面我们来看下缺省的三种情况:
全缺省:
void add(int a,int b,int c)//全缺省参数都没有缺省值
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
add();//有缺省参数时,不传参使用缺省值;
return 0;
}
半缺省:
void add(int a,int b,int c=10)// 半缺省参数必须从右往左依次来给出,不能间隔
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
add();//有缺省参数时,不传参使用缺省值;
return 0;
}
注意:
· 带缺省值的参数必须放在参数列表的最后面;
· 缺省参数不能同时在函数声明和定义中出现,只能一个出现;
· 缺省值必须是常量或者全局变量;
4.函数重载
//例如:
void swap(int a,int b);
void swap(char a,char b);
void swap(double a,double b);
//这种在c语言中不能实现,只能为每个函数单独取名
在c语言中我们实现一个函数,只能传固定值进去,也就是一个函数名只能实现一种方式。在c++中函数重载,可以实现相同函数名传不同类型变量进去,实现一个作用域内用同一个函数名命名一组功能相似的函数,减少函数名的数量,避免名字空间的污染,对程序的可读性也有提升。
函数重载的规则:
1.函数名必须相同
2.函数重载的参数个数,参数类型或参数顺序三者中必须有一个不同
3.返回类型可以相同也可以不相同
4.如果仅仅返回类型不同不能构成函数重载
在C语言中不支持重载,是因为C是通过函数名来区分不同函数。而C++区分函数是通过函数内的参数进行分别,只要参数不同,修饰出来的名字就不一样,因此可以重载,也就解释了规则第4条(仅仅返回类型不同不能构成函数重载)。
5.引用
5.1定义
引用是给已存在变量取了一个别名,不是定义一个新的变量, 编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。类比到人的话就像是给人取外号,外号所指的人还是那个,只是名字不同而已。
如何使用引用呢?
类型& 引用变量名=引用的对象名;
int main()
{
int a = 0;
int& b = a;//这里的b就是a只是名字不同
//需要注意的是:引用类型必须和引用实体是同种类型的
return 0;
}
5.2引用特点
1.引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 没有空引用也没有二级引用
4. 引用一旦引用一个实体,再不能引用其他实体
5. 一个变量可以有多个引用(相当于一个变量有好几个别名)
int main()
{
int a = 0;
int& b = a;
int& c = NULL;//不用空引用
int&& d = a;//不能二级引用
int& e;//没有初始化
return 0;
}
5.3指针和引用区别
引用和指针的不同:
1.引用概念上定义一个变量的别名,没有独立空间,和引用的实体是共用同一块空间。指针存储一个变量地址。也就是程序为指针变量分配内存区域;而不为引用分配内存区域。
int main()
{
int b = 10;
int* a = &b;
int& c = b;//c没有分配新的空间
return 0;
}
2.指针使用时要前面加解引用*,而引用就可以直接使用其余由编译器处理
int main()
{
int a = 0;
int b = 0;
int* aa = &a;
int& bb = b;
*aa = 10;//指针使用需要加*
bb = 100;//引用就可以直接使用
return 0;
}
3.引用在定义时必须初始化,指针没有要求
4.指针变量可以指想任意同类型的实体,引用在定义时就被初始化不能改变(因为引用只是为了取别名改变后实例对象已经发生改变)
5.在sizeof时引用的结果为引用类型的大小,而指针时指针变量的大小(即32位下占4字节)
6.引用不能空引用,但是指针可以指向NULL
7.指针可以有多级指针,但是没有多级引用
8.指针变量作为形参时,需要检查是否为NULL,引用则不用判空
9.对指针变量的操作,会使指针变量指向下一个实体(变量或对象)的地址;而不是改变所指实体(变量或对象)的内容。
对引用的操作直接反应到所引用的实体(变量或对象)。
例如:指针的++是向后偏移一个类型的大小,而引用是引用的实体+1
因此我们可以看到c++中指针和引用都有各自的用处,两者相互补充。
5.4使用场景
1.在C++中引用做参数
void Swap(int& left, int& right)//节省指针解引用过程
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 0;
int b = 4;
Swap(a, b);
return 0;
}
2.做返回值
我们先来看下这个场景
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;//会创建临时变量
}
int Add(int a, int b)//传值返回
{
static 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;
}
上面两组代码中,Add函数将c的值返回给ret,那么是否是直接将c返回?通过c语言的学习我们可以知道,c是Add函数内的出了作用与就会被销毁,因此应该不是直接返回c,但是第二组代码中,c为静态的处理作用域还在,那么是直接返回c吗?答案是没有,编译器为了同一无论出了作用域是否存在都会将返回的值放入临时变量,然后将临时变量返回,这就是传值返回。在第一组代码中c在函数内创建的,没有办法,但是第二组代码中我们是否有办法节省掉临时变量——结果是引用返回,第二组代码中c为静态变量,出了作用域还在,因此我们使用传引用返回,就是给c取别名,实质还是c本身。
int& Add(int a, int b)
{
static 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;
}
因此在什么样的场景下可以使用传引用返回?我们可以看放到引用时取别名,因此函数返回时,出了函数作用域,如果返回对象还在(出了作用域还在),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
5.4传值返回和传引用返回 比较
上面我们具体介绍了传引用返回和传值返回,那么两者的效率有什么不同?
在传参和返回期间,以值作为参数或者返回值类型, 函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率很低,当参数或者返回值类型非常大时,效率就会更低。
6.内联函数
调用函数如果我们的函数体代码较多的话,需要的执行时间相应也会增加,则函数调用占用的时间可以忽略;如果函数的代码较少,大部分时间都在函数调用上,则时间的开销就不少了。因此c++中就引入了内联函数的概念。
定义:
inline修饰的函数叫内联函数,编译时编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
inline int& Add(int a, int b)
{
static 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;
}
需要注意的是:
- inline对于编译器而言只是建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性****如果函数体内出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间将比函数调用的开销大得多。
- 要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。
- inline是一种以空间换时间,编译器将函数视作内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
这样看是不是觉得与c语言中的宏函数有些类似?
宏函数的优点是
1.增强代码的复用性
2.提高性能
宏函数的缺点是:
1.不方便调试宏。(预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
因此我们需要根据不同场景灵活使用内联函数,宏函数等