C++入门一
一、命名空间
#include <stdio.h>
int rand = 0;
int main()
{
printf("hello world\n");
printf("%d\n", rand);
}
这样是可以运行的,可是当我们加入一个头文件的时候
#include <stdio.h>
#include <stdlib.h>
int rand = 0;
int main()
{
printf("hello world\n");
printf("%d\n", rand);
}
这是因为c语言面临着一个命名冲突的问题,当我们定义一个变量或一个函数的时候,有时候发现就会和库里面的函数起冲突
库里面的会尽量和你避免起冲突,大不了就是换一个变量,但是当你和别人共事的时候发现变量起冲突就麻烦了
所以C++在这里设置了一个叫命名空间的东西
namespace stn {
int rand = 0;
}
int main()
{
printf("hello world\n");
printf("%p\n", rand);
printf("%p\n",stn::rand);
}
但是我们给一个rand变量的时候,它是会到命名空间去找,还是去到全局去找呢?
这里默认的是到全局去找
这里又引申出来了一个c++关键字 ::域作用限定符
就好比我把这个变量用围栏围起来,你要使用这把钥匙才能去访问它
我们可以用这个命名空间中定义变量/函数/类型
namespace stn {
int rand = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int Add(int left, int right)
{
return left + right*10;
}
int main()
{
printf("%d\n",stn::rand );
printf("%d\n", stn::Add(1,2));
struct stn::Node node;
return 0;
}
这样子就不会存在命名冲突的问题了
这样子我们每次都要指定就会有点繁琐,有一种展开命名空间的方法
using namespace stn
这就相当于把围栏给撤了,默认也都可以去命名空间里面去找,但是这时候也还是会又出现上面那种报错的形式,所以还是上面指定的方式最安全
但是我们有时候一种函数比如说命名空间里面Add函数调用的非常的多
这时候我们采用一种叫部分展开的一种形式
using stn::Add;
对于常去调用的我们去采用部分展开
但如果部分展开发现也会重名的话这时候我们就不能采用部分展开
命名空间还有一种方法是命名空间套用命名空间
namespace stn {
int rand = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
namespace xxx
{
int rand = 1;
}
}
int main()
{
printf("%d\n", stn::xxx::rand);
return 0;
}
这样子我们就能访问套用命名空间里面的,一般也不会套的太深
这时候我们就能引出c++的标准库命名空间了
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
c++库里面的所有东西都会放到这个标准库里面避免你命名的东西和它命名的东西冲突,你定义的就不会和它起冲突这样就会舒服很多了
或者有的不想展开标准库的命名空间
#include <iostream>
namespace std
{
};
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
int i = 0;
std::cin >> i;
return 0;
}
也可以采用这样的操作
二、流插入流提取运算符
<<
这个官方一点叫作流插入运算符
流向这个cout里面
endl代表换行符,你使用这种形式也是可以的
std::cin >> i;
这个叫作流提取,提取是不用换行的
另外一点
int i ;
double j;
std::cin >> i>>j;
cout << i<< endl;
cout << j << endl;
cout << &i << endl;
cout << &j << endl;
这个是可以自动识别类型的
三、缺省参数
缺省参数是声明或定义函数时为函数的的参数指定一个缺省值,在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参
void Func(int a = 1)
{
cout << a << endl;
}
int main()
{
Func(2);
Func();
return 0;
}
第一种是像这种正常的调用,第二种则是没传则用这个缺省参数
全缺省参数
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();
Func(1);
Func(1,2);
Func(1, 2,3);
return 0;
}
这种叫作全缺省参数,我们可以给一个或者多个参数这时候编译器会自动根据我们给的实参去调整应该给多少的形参
半缺省参数
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);
Func(1, 2);
Func(1, 2, 3);
return 0;
}
半缺省参数必须从右往左给缺省值,且必须给一个及以上不能一个都不给
接下来演示一个缺省参数有意义的场景
namespace stn
{
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps,int N=4)
{
ps->a =(int*)malloc(sizeof(int)*N);
ps->top = 0;
ps->capacity = 0;
}
}
int main()
{
stn::ST st;
StackInit(&st,100);
//不知道可能会插入多少个
stn::ST st1;
StackInit(&st1);
return 0;
}
我们当不知道开多少个空间就可以采用这样的,倘若我们一次次push的话就需要扩容,而扩容是有代价的,会导致空间的浪费,这样就可以很好的避免空间的浪费
注意:缺省参数声明和定义这样子同时给会报错
void StackInit(ST* ps, int N = 4);
#include "Test.h"
void stn::StackInit(ST* ps, int N = 4)
{
ps->a = (int*)malloc(sizeof(int) * N);
ps->top = 0;
ps->capacity = 0;
}
就是如果你声明和定义给不同的缺省值的时候编译器不知道用的是哪个
所以c++在规定了声明给,定义不给
如果你在两个.cpp文件里面都包含了一个函数,就会生成两个链接,导致编译的时候起冲突,这时候我们就要检查.h里面有没有给这个函数的声明
或者可以加static影响链接属性,static可以让它只在当前文件可见
四、函数重载
函数重载:是函数的一种特殊状况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
c语言不允许同名函数,cpp可以,但是要求构成函数重载
函数名相同,参数不同
int Add(int left, int right)
{
cout << "int Add(ing left,int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "int Add(double left,double right)" << endl;
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
cout自动识别类型其实也是一个函数重载
void f(int a, char b)
{
cout << "f(int a, char b)" << endl;
}
void f(char a, int b)
{
cout << " f(char a, int b)" << endl;
}
这是类型构成函数重载
注意:返回值不同并不能构成重载
namespace stn1
{
void func1(int x)
{
}
}
namespace stn2
{
void func1(char x)
{
}
}
这两个并不构成函数重载因为它们作用在不同的域里面,完全相同也无所谓
namespace stn1
{
void func1(int x)
{
}
}
namespace stn1
{
void func1(char x)
{
}
}
这就会构成函数重载了,因为同一个命名空间会进行合并
void func(int a)
{
cout << "void func(int a)" << endl;
}
void func(int a,int b=1)
{
cout << "void func(int a,int b=1)" << endl;
}
这也是构成重载,个数不同,但是调用的时候会存在歧义
就是你输入一个参数的时候不知道是去调用第一个还是第二个
那么c++为什么能识别函数重载,而c语言却不能识别呢,这是因为一个程序要运行起来,需要经历以下几个阶段:预处理,编译,汇编,链接
函数调用本质生成的指令是
call(跳转) Func(地址)
Test.cpp
//预处理 头文件展开/宏替换/去掉注释//条件编译
Test.i
//编译 检查语法,生成汇编代码(指令级代码)
Test.s
//汇编 将汇编代码生成二进制机器码
Test.o
//链接 合并连接,生成可执行程序
根据这个call指令会生成一个符号表,这个符号表是变量和地址的一个映射
c语言是直接用这个func去充当它的名字
这时候就会引入一个函数名修饰规则
void func(int a, double b);
void func(double a, int b);
int main()
{
func(1, 1.1);
func(1.1, 1);
return 0;
}
当我们只有声明去调用的时候就会报链接错误
这里我们可以看出一些差别HN和NH 说明编译器是将int当作H,double当作N的,这就是修饰以后的函数名(VS的);
Linux修饰的会更加简单_Z4idfunc();,_Z是这个函数的前缀,4是这个函数的字节数,i是int,d是double
函数编译的时候会将函数的第一行指令当作地址,所以你函数只有声明没有定义的时候是没有地址的
所以两个名字找到的其实是两个不同的地址,然后再去call
得到了返回值不同就不能构成函数重载,因为函数名修饰规则就没有带入返回值
问题
函数名修饰规则带入返回值,返回值能不能构成函数重载
这是不能的就像上面func(1.1,1)你只有返回值不同的话怎么去区分这两个函数,要代入到哪个函数里面去
C语言和C++的语法规则还是存在着较大的变化的,有什么写的不对的地方欢迎大家指出