前言
一路磕磕绊绊,也算是走到了C++的大门下。C++从名字上就可以看出是C语言的“plusplus版本”,C++在兼容C语言的基础上又加入了许多方便又高深的特性与机制,便于我们更容易处理C语言中的棘手问题。不得不提的一点是C++为我们打开了面向对象思想的大门,过去我们写过的C语言都是遵循面向过程的思想,而C++的面型对象在面对复杂的问题,规模较大的 程序,需要高度的抽象和建模时,相较于C语言要方便很多,所以是一门很值得学习的语言。
废话就不多说了,我们直接步入正题,踏入C++的世界吧。
1. 命名空间
命名空间的提出主要是解决C语言中命名冲突的问题。在C语言中第三方库、标准库、自己定义的名字等等之间不可避免的存在命名相同的情况,这个时候只能对命名进行修改。
int main()
{
int rand = 2;//在C语言中会出现重命名的情况,因为rand是一个库函数名
return 0;
}
在C++中,引入了命名空间的概念,通俗理解就是将各种标识符的名称汇集起来放在一个命名空间中,而许多这样不同空间就可以使得虽然名字相同,但处在不同的命名空间内,不会产生冲突。
1.1 命名空间的定义
命名空间的定义使用关键字namespace,然后接命名空间的名字,然后使用{}包含所有命名空间的成员。需要注意:①命名空间中可以定义变量、函数、类型;②命名空间可以嵌套定义;③如果定义了相同名称的命名空间,编译器会将其合并为一个。
//命名空间的定义
namespace np1
{
//命名空间内可以定义变量、函数、类型,也可以嵌套其他命名空间
int a = 9;
int Add(int a, int b)
{
return a + b;
}
struct Node
{
struct Node* next;
int val;
};
namespace np2
{
int a = 8;
char c;
int Sub(int a, int b)
{
return a - b;
}
}
}
//相同名称的命名空间最后会被编译器合并成为一个
namespace np1
{
int b;
}
namespace np2 //该命名空间不会和np1中的np2合并
{
int b = 6;
short a = 7;
}
1.2 命名空间的使用
命名空间的使用有三种方式:①命名空间 + ::(域作用限定符);②using引入某个成员;③using namespace引入整个命名空间。
namespace np1
{
int a = 9;
int Add(int a, int b)
{
return a + b;
}
struct Node
{
struct Node* next;
int val;
};
namespace np2
{
int a = 8;
char c;
int Sub(int a, int b)
{
return a - b;
}
}
}
namespace np1
{
int b;
}
namespace np2
{
int b = 6;
short a = 7;
}
//命名空间的使用
//1.命名空间 配合 ::
// :: -- 域作用限定符
int main()
{
printf("%d ", np1::a);
printf("%d ", np1::Add(2,6));
return 0;
}
//2.通过using引入命名空间的某个成员
using np1::np2::a;
using np2::b;
int main()
{
printf("%d ", a);
printf("%d ", b);
return 0;
}
//3.通过using namespace引入整个命名空间
using namespace np1;
int main()
{
printf("%d ", Add(7, 9));
printf("%d ", a);
printf("%d ", np1::np2::a);//因为np1被展开了,所以np2的指向不明确,np1::np2表示np1中的np2
printf("%d ", ::np2::a);//::np2表示全局域下的np2
return 0;
}
1.3 变量名搜索顺序
我们首先要理解域的概念。在C语言的学习过程中,我们知道全局域和局部域的概念,在此处我们再介绍一个命名空间域,很明显就是命名空间内的区域。除此之外还有类域的概念我们以后会接触到。
对于一个变量,如果指定了域则会直接到域中搜索该名称;否则就遵循先搜索局部,后搜索全局的顺序。在C++中,可以使用域作用限定符来表示全局变量。
//域:全局域、局部域、命名空间域、类域
//对于一个变量名:如果没有指定域则先搜索局部,后搜索全局
// 如果指定了域则直接去对应域中搜索
namespace np1
{
int a = 9;
namespace np2
{
int a = 8;
char c;
}
}
namespace np2
{
int b = 6;
short a = 7;
}
int a = 4;
int main()
{
int a = 2;
printf("%d\n", a); //2
{
printf("%d\n", a); //2
}
printf("%d\n", ::a); //4 此处的::表示全局变量
printf("%d\n", np1::a); //9
printf("%d\n", np2::a); //7
return 0;
}
2. 输入输出
在想要学习一门语言时,我们的第一步自然就是用它向世界问号,那么C++如何打印hello world呢?
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
我们来逐行对这个简单的代码进行理解。
第一行是包含头文件的操作,iostream就是一个标准输入输出流文件。但是我们发现和C语言不同的是没有了后缀.h,这是为了和C语言区分。
第二行是使用命名空间std。std是C++标准库的命名空间,C++标准库的定义都在这个命名空间中。由此我们发现,头文件中有的只是函数、变量的声明,而定义部分都位于std命名空间内。
main函数内就是打印hello world的部分。cout是标准输出对象(控制台),对应的cin是标准输入对象(键盘),endl表示输出换行。而<<则是流插入运算符,cout<<"hello world"表示向cout(控制台)插入字符串。同样的,>>就是流提取运算符。
在C++的输入输出是,不再像printf和scanf一样需要输入%d等规定类型,C++可以自动识别变量类型。
#include<iostream>
using namespace std;
int main()
{
// <<
//1.左移
int a = 2;
int b = a << 2;
//2.流插入
const char* str = "ccc\n";
cout << "hello world" << a << str << b << endl;
// >>
//1.右移
//2.流提取
cin >> a >> b;
cout << a << b;
return 0;
}
3. 缺省参数
缺省参数就是在声明或定义函数时,为函数的参数设定一个缺省值。在调用函数的时候,如果指定了参数,则用实参;如果没有给定实参,就使用缺省值作为参数。
void func(int a = 10, int b = 33)
{
cout << a << ' ' << b << endl;
}
int main()
{
func(7, 9); //7 9
func(23); //23 33
func(); //10 33
return 0;
}
缺省分为全缺省和半缺省两种。全缺省就是所有参数都有缺省值,而半缺省则是部分参数有缺省值。缺省值必须是常量或者全局变量。
void func1(int a = 10, int b = 33) //全缺省
{
cout << a << ' ' << b << endl;
}
void func2(int a, int b = 79, int c = 88) //半缺省
{
cout << a << ' ' << b << ' ' << c << endl;
}
int main()
{
func1(7, 9); //7 9
func1(23); //23 33
func1(); //10 33
func2(1,62,35); //1 62 35
func2(48,29); //48 29 88
func2(12); //12 79 88
//func2(); //error
return 0;
}
函数调用时会传参,这时实参会按顺序传给形参,知道实参全部使用完毕。如果这时发现还有参数没有值,那么就使用给出的缺省值。因此我们得到结论:传递的参数是从前向后依次传递,缺省值应从后向前覆盖,即一个参数有缺省值就说明其后的参数都有缺省值。值得再次强调的是传参不会跳着传,缺省值也不可以跳着给。
void func(int a = 10, int b) //error
{
cout << a << ' ' << b << endl;
}
另一个需要明确的事情是当定义和声明分离,缺省值应该给定义还是声明还是两个都有,不同该怎么办?
首先给出答案,在定义和声明分离时,缺省值必须且只能在声明时给出。
对于这样的代码:
test.cpp
#include"test.h" int main() { func(1); return 0; }
func.cpp
#include"test.h" void func(int a, int b) { cout << a + b << endl; }
test.h
#pragma once #include<iostream> using namespace std; void func(int a = 2, int b = 7);
首先明确的一点是,链接前各个源文件各自编译生成可重定位目标文件。之所以称为可重定位是因为其中的汇编出来的函数地址并不是真正的地址,所以需要链接后再确定。但是我们知道在生成可重定位目标文件后,其机器码需要改变的只是重定位后的地址。换言之,就是调用函数前调用参数已经被压入了栈中。所以缺省值如果在定义处给出,那么此处的调用参数就难以确定,所以必须在声明处给出缺省值,这样参数才可以顺利压入栈。
对于定义和声明在同一个源文件的情况,缺省值不可以在声明和定义同时出现,换言之,任何一者有缺省值,另一个没有都是正确的。
void func1(int a, int b = 10) //correct
{
cout << a + b << endl;
}
void func1(int a,int b);
void func2(int a, int b) //correct
{
cout << a + b << endl;
}
void func2(int a,int b = 10);
void func3(int a, int b = 10) //error
{
cout << a + b << endl;
}
void func3(int a,int b = 10);
在借鉴前辈们的心得时发现还有类中等其他情况下的缺省,暂时还接触不到,未来学习到了再做以总结。