手撕C++初阶语法鬼子

其实C++初阶的语法都是在填C语言的坑,C++祖师爷写C语言的时候看到了很多的问题,觉的一门语言难用就写一门比它好用的语言,可太牛鼻了。

1.namespace

1.1简介

解决C语言命名冲突。

1.2用法

用法有点像结构体,可以在namespace里面定义变量,函数,和类型。还可以嵌套使用。使用的时候在前面加上'::' 域作用限定符就可以访问namespace里面的定义的东西,如果在namespace定义一个变量但是你在引用的时候不加域作用限定符,编译器就相当于完全看不到这个变量。

namespace n
{
  int a = 0;
  int add(int a ,int b)
  {
    return a+b;
  }
  struct Node
  {
   struct Node* next;
	int val;
   };
  namespace x
  {
    int rand = 2;
   
  }

}
int main()
{
   int a = 1 ,b = 2;
   printf("%d",n::a);
   n::add(1,2);
   struct n::node node;
   printf("%d"n::x::rand);
  return 0;
}

1.3namespace的展开

有时候一个函数或者变量和类型需要频繁使用的时候可以把namespace进行局部展开,或者全部展开(不建议容易出问题)。

像c++的标准库的函数和对象都是在 namespace std 中声明的,这和C语言的区别就出来了,即使包含了头文件,我们也不能直接使用C++标准库,因为声明在namespace std里面,不展开或不使用'::',编译器根本找不到。

useing namespace n;//全局展开
useing n::add;//局部展开

1.4namespace的合并

例如 在test1.h 定义了一个namespace n 又在test2.h中定义了一个namespace n ,在test.c中引用了test1.h和test2.h,这时候两个头文件的namespace n 就合并成了一个。

1.5注意

不建议在namespace中定义函数,因为有多个.c文件去包含namespace所在的头文件的时候,会出现函数的重定义问题,也可以用static取消在namespace中定义函数的外部链接属性,在别的文件中就找不到了,就不会出现重定义的问题。

2.缺省函数

2.1简介

调用函数的时候可以不传参数或者只传部分参数。

2.2用法

void fun(int a = 1)
{
   printf("%d",a);
}

int main()
{
  fun();
  fun(2);
  return 0;
}

第一次不传参数调用的结果就是1,第二次传了个2调用的结果就是2。

2.3全缺省和半缺省

全缺省:在定义函数的时候所有的参数都是已经初始化了,不传参数也可以直接调用,像上面的fun就是一个全缺省。

半缺省:在定义函数的时候不是所有的参数都被初始化,调用的时候必须传参数。

void add(int a ,int b = 2)
{
  return a+b;
}

2.4注意

半缺省必须从右向左给缺省值,举个例子。

void fun1(int a = 1,int b ,int c =2)
{
  return 0;//错误示范1
}

void fun2(int a =1 int b =2 ,int c)
{
  return 0;//错误示范2
}

void fun3(int a ,int b =2,int c=3)
{
  return 0;//正确示范
}

3.函数重载

3.1简介

在C语言中是不允许有同名的函数的出现的,但是在C++里只要两个同名函数函数构成了重载,那么这两个函数就可以同时存在。

3.2用法

两个同名的函数只要这两个函数的参数个数,参数类型,参数类型的顺序,有一个不同就可以构成函数重载。

void fun(int a,int b)
{
  return 0;
}

void fun(double a,double b)
{
 return 0;
}

void fun(int a,double b,double c)

{
  return 0;
}

void fun(double a,int b,double c)
{
  return 0;
}

像我们调用fun(a,b),a和b都是整形的话,调用的就是第一个fun。

如果fun(a,b,c),a是整形,b和c是双精度浮点型,调用的就是第三个函数。

3.3注意

返回值的不同不能构成重载。

例:这两个函数不构成重载。

int add (int a,int b)
{
   return a+b;
}

double add(int a, int b)
{
   return a+b;
}

 4.流插入和流提取

4.1简介

打印和输出

4.2用法

#include<iostream>

useing std::cout;//打印printf
useing std::endl;//换行'\n'
useing std::cin;//输入scanf

int main()
{
  int i; 
  cout<<"helloword"<<endl;
  cin>> i >>endl;
  cout<< i <<endl;
  return 0;
}

这里和C语言有不一样的地方,就是不用输入 打印和输出的类型 ,是不是很神奇,其实本质就是函数的重载,我打印的是整形就调用打印整形的cout,打印浮点型就调用打印浮点型的cout。

5.引用

5.1用途

引用传参,和引用返回

5.2用法

就是在类型变量名字之间加了一个&,下面这个代码,b就是a的引用,你可以理解为b就是a的别名。

int main()
{
 int a = 1; 
 int & b = a;
 return 0;
}

这个就相当于给a起了一个外号b,假如我叫王五,我的舍友给我取了一个外号叫福根,这个时候福根是我,王五也是我。

打印a和b的数值和地址都是相等的也是直接说明了a和b是在同一块空间的,切记b不是a的拷贝,你改a的值,b也会跟着变。 

5.3引用的特性

1.引用在定义的时候必须初始化。

2.一个变量可以有多个引用。

3.引用一旦有初始化,就不能再初始化了。

5.4引用传参

1.引用传参可以提高效率

给函数传参的时候,形参是实参的一份临时拷贝,如果我的实参特别大,传过去的时候就需要在函数的栈帧里开辟一大块空间,然后再把实参拷贝过去,非常的不人性。

但是如果我们直接传引用,我就相当把实参不拷贝,也不开辟空间,直接传过去了,大大的节省了时间非常的人性。

2.引用传参形参可以修改实参

例如:我们在写一个交换函数的时需要把,实参的指针传过,通过指针去修改交换的实参,非常的麻烦,这个时候如果我们用引用做参数,即使不传指针,也可以完成交换,非常的人性。

void swap(int &a,int &b)
{
  int ret = a;
  a = b;
  b = ret;
}

int main()
{
  int x = 4 , int y = 5;
  swap(x,y);
}

5.5传返回引用

1.传返回引用可以提高效率

正常我们函数返回值的时候,这个函数已经结束,函数的栈帧已经销毁,这个时候返回值已经被拷贝到一个具有常属性的临时空间,然后再把这个临时变量返回给main函数。

但是切记我们用传返回引用的时候,返回值不可以是出了这个函数的作用域就销毁的变量,

就像下面这个c肯定就是不行的,函数结束的时候空间都销毁了,如果没建立新的栈帧还能打印出来,建立新的栈帧之后,这块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;    
    return0;
}

实际的输出结果却是这个,因为c出了作用域被销毁了,然后又调用了Add(1,2)重新建立栈帧把原来c的空间覆盖了。

2.传引用返回可以修改返回对象

正常函数返回是把返回值拷贝回来,无论怎么修改都不会影响,传引用返回就相当把这个空间的钥匙给你了,你想怎么改都可以。

5.6引用和指针的区别

引用和指针在上层来说,引用是和实体共用一块空间,是实体的别名,没有自己的独立空间。

但是在底层来看,引用就是用指针来实现的,所以引用本身是有空间的。

这是引用和指针的汇编代码

lea指令的就是取地址的意思

都是取出地址放到eax里然后 eax把取出的地址放到ra和pa中。

5.7引用的权限问题

引用的权限不能扩大,只能平移或者缩小。

例:

int func()
{
	int a = 0;
	return a;
}

int main()
{
	const int& ret = func();//权限的平移
	const int a = 0;
	// 权限的放大
    int& b = a;//a是加了const的具有了常属性,但是b没有常属性,就相当把a的权限放大了
	int b = a; //可以的,因为这里是赋值拷贝,b修改不影响a
	// 权限的平移
	const int& c = a;
	// 权限的缩小
	int x = 0;
	const int& y = x;
	int i = 0;
	const double& d = i;
	return 0;
}

6.内联函数

6.1简介

这个也是祖师爷为了填C语言宏函数的坑设计出来的,宏函数非常容易出错,可能让祖师爷用的很不爽。

以inline修饰的函数叫做内联函数,在调用时会在调处进行展开,展开简单理解,就是把调用处给替换了,像宏一样,这么做可以不调用函数,就可以减少栈帧的开销

6.2用法

正常函数调用的时候会有call的指令,很明显这个是替换过去的。

6.3注意

1.inline只是我们给编译器的一个建议,编译器用不用,我们说的不算。

2.inline最好是用在函数比较小并且会经常复用的函数,递归是用不了的。

3.inline的声明和定义必须在一起,不然会出现链接错误,因为inline被展开了,没有这个函数的地址了,就找不到这个函数了。

7.auto

7.1简介

auto 可以推导参数类型。

7.2用法

std::vector<std::string>::iterator 是一个类型的名字,很长很长,这个时候直接一个auto接收,简直不要太香。

	std::vector<std::string> v;
	std::vector<std::string>::iterator it = v.begin();
	auto it = v.begin();

7.3注意

1.auto在声明引用类型的时候必须加& auto & a  =  x; 

2.使用auto同一行定义多个变量时,切记前后的类型要是一样的,因为编译器只会推导第一个类型,然后用推导出来的类型定义其他变量。

错误示范

auto a = 3,b = 1.00;

3.auto 不能做参数

4.auto不能直接声明数组

8.范围for

8.1简介

可以自动帮程序员确定范围

8.2用法

下面这两行代码是一样的 。 大概意思 就是 把数组array的内容取出来放到e 中,自动的迭代,和停止。


for (auto e : array)
{
  cout << e << " ";
}
for(int i = 0;i<sizeof(array)/sizeof(int);i++)
{
  count << array[i] << " ";
}

8.3注意

数组的范围一定是确定的才可以用。

9.nullptr

9.1简介

就是C++定义的空指针。

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。
这么定义有一些情况就会出问题。
void f (int a)
{
  cout<<"fun(int a)"<<endl;
}

void f (int* a)
{
  cout<<"fun(int*a)"<<endl;
}


int main()
{
   f(NULL);
   f((int*)NULL);
}

我们的本意是想传个空过去,去调用一下这个指针版本的函数,但是因为NULL的值是0,所以第一调用的就是整形的版本。

所以以后为了代码的强健度建议使用nullptr

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值