文章目录
目录
前言
1.C++关键字
1.1命名空间
1.2命名空间定义
1.3命名空间的使用
2.C++输入&输出
3.缺省参数
3.1缺省参数的概念
3.2缺省参数分类
4.函数重载
4.1函数重载的概念
5.引用
5.1 引用特性
5.2 常引用
5.3引用的使用场景
5.4引用和指针
6.内联函数
6.1内联的特性
7.宏
8.auto关键字(c++11)
8.1 auto的使用细则
9.基于范围的for循环(语法糖)
9.1范围for的语法
9.2范围for的使用条件
10.指针空值--nullptr(c++11)
总结
前言
什么是C++:
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,需要高度的抽象和建模时,C语言则不合适。C++是基于C语言产生的,它可以进行C语言的过程化程序设计,又可以以抽象数据类型为特点的,基于对象的程序设计,还可以进行面向对象的程序设计。
1.C++关键字
C++总计63个关键字,如下图所示,具体用法可参考后续文章。
1.1命名空间
在c/c++中,变量,函数和后面需要学的类都是大量存在的,这些变量,函数和类的名称都存在于全局作用域中,会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或者名字污染,namesapce关键字就是针对这种问题的。
1.2命名空间定义
定义命名空间,需要使用namespace关键字,后面跟命名空间的名字,接一对{}即可,{}中即为命名空间的成员
1、正常的命名空间定义
namespace Queue{
//命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left+right;
}
struct Node
{
struct Node * next;
int val;
}
}
2、命名空间可以嵌套
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left+right;
}
namespace N2
{
int c;
int d;
int sub(int left ,int right)
{
return left -right;
}
}
}
3、同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成在同一个命名空间中
//test.h
namespace N1
{
...
}
//test.cpp
namespace N1
{
...
}
test.h和test.cpp中的两个N1会被合并成一个
注意:一个命名空间就定义了一个新的作用域,命名空间中所有的内容都局限于该命名空间中。
1.3命名空间的使用
命名空间使用有三种方式:
- 加命名空间名称以及作用域限定符
- 使用using将命名空间中的某个成员引入
- 使用using namespace 命名空间名称引入
namespace byte
{
int a = 0;
int b = 1;
int Add(int left,int right)
{
return left+right;
}
...
}
int main()
{
printf("%d" ,a);
}
1.加命名空间名称以及作用限定符
int main()
{
printf("%d" ,byte::a);
}
2.使用using将命名空间某个成员引入
using byte::b;
int main()
{
printf("%d" ,b);
}
3.使用using namespace 命名空间名称引入
using namespace N;
int main()
{
printf("%d" ,a);
}
2.C++输入&输出
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world!"<<endl;
}
说明:
1.使用cout标准输出对象(控制台)和cin标准输入对象时,比如包含<iostream>头文件,以及按命名空间使用方法使用std;
2.<<是流插入运算符; >>是流提取运算符
3.cout和cin是全局的流对象,endl是特殊的c++符号,表示换行输出,都包含在iostream头文件中
4.使用c++输入和输出更方便,c语言中使用printf和scanf需要手动控制格式,c++可以直接自动识别变量类型
注意:早期标准库将所有功能在全局域下实现,声明在.h后缀的头文件中,使用时包含对应头文件即可,后来在std的命名空间下,和c头文件进行区分,规定c++头文件不带.h,推荐使用<iostream>+std的方式
#incllude<iostream>
using namespace std;
std命名空间的使用惯例:
std是c++标准库的命名空间
1.日常使用中,建议直接使用using namespcae std即可
2.using namespace std展开,标准库都暴露出来,自定义和库重名的类型/对象/函数就存在冲突问题,在开发项目中使用: std::cout; using std::cout展开常用的库对象/类型等方式
3.缺省参数
3.1缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值,在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
count<<a<<endl;
}
int main()
{
Func(); //没有传参时,使用参数的默认值
Func(10); //传参时,使用指定的实参
return 0;
}
3.2缺省参数分类
- 全缺省参数
void Func(int a = 10 ,int b = 20, int c = 30)
{
count<<a<<endl;
count<<b<<endl;
count<<c<<endl;
}
- 半缺省参数
void Func(int a, int b = 10,int c = 20)
{
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
注意:
1.半缺省参数必须从右往左依次给出,不能间隔给
int main() { Fun(1); Func(1,2); Func(1,2,3); Fun(,20,)//err //调用的时候赋值从左往右,缺省参数给的时候从右往左 }
2.缺省参数不能在定义和声明中同时出现
//如果声明和定义同时出现,两个位置提供的值不同,编译器无法确定使用哪个缺省值
4.函数重载
4.1函数重载的概念
函数重载:是函数的一种特殊情况,c++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似,数据不同的问题。
#include<iostream>
using namespcae stdl
//1.参数类型不同
int Add(int left,int right)
{
return left+right;
}
double Add(double left,double right)
{
return left+right;
}
//2.参数个数不同
void f()
{
count<<"f()"<<endl;
}
void f(int a)
{
cout<<"f(int a)"<<endl;
}
//3.参数类型顺序不同
void f(int a,char b)
{
...
}
void f(char b, int a)
{
...
}
支持重载的原理:
在c/c++中,一个程序运行起来需要:预处理,编译,汇编,链接
1.实际项目通常是由多个头文件和源文件组成,当前a.cpp中调用了b.cpp中定义的add函数时,编译后,链接前,a.o中目标文件没有add的函数地址,因为add是在b.cpp中定义的,在链接阶段处理这个问题,链接器看到a.o中调用add,但是没有add的地址,就会到b.o的符号表中找到add的地址,然后链接在一起
2.在链接时,面对add函数,去找这个函数名字(通过g++修饰的规则),编译器将函数参数类型信息添加到修改后的名字中
3.C++就是通过函数修饰来区分,只要参数不同,修饰出来的名字就不同,构成了函数重载。
4.如果两个函数,函数名和参数相同,但是返回值不同,无法构成重载,因为编译器无法区分。
5.引用
引用不是定义了一个新变量,而是给已存在的变量取别名,编译器不会为引用变量开辟新的内存空间,它和它引用的变量共用一块内存。
void Test()
{
int a = 10;
int& ra = a;
}
//引用类型必须和引用实体是同种类型的
5.1 引用特性
- 引用在定义的时候必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
5.2 常引用
void Test()
{
const int a = 10;
// int& ra = a ; err,a为常量
const int & ra = a;
//int& b = 10 ; //err,b为常量
const int& b = 10;
double d = 13.15;
//int & rd = d; //err 类型不同
const int& rd = d;
}
5.3引用的使用场景
- 作参数(输出型参数,形参的改变影响实参)
C语言中,如果使用一般形参,形参是实参的临时拷贝,则定义了指针去改变。c++中传引用。
void swap(int& r1, int&ra) { ... } int main() { int a = 0, b = 1; swap(a,b); } //递归里面,如果直接传变量,这层调用完,形参改变, //并不会改变上一层的参数,这时候也可以使用引用
- 作为返回值
int &Count() { static int n = 0; //n存在静态区 n++; //... return n; }
int Count() { static int n = 0; //无static n是临时变量,定义在count栈里面,出了作用域返回, //n不能直接给ret 此时count已经调用完成,栈帧销毁。 此时n给一个临时变量,这个临时变量作为函数的返回值 如果比较小,通常使用寄存器充当 //有static n 存在静态区,栈销毁不会影响n,n也会给一个临时变量作为返回值(不会把n直接给ret) n++; return n; } int main() { int ret = Count() return 0; }
上述代码也有优化的空间,可以使用引用去作为返回值,减少拷贝。
出了作用域,它的声明周期还在,就可以使用传引用返回。
//传引用返回,产生了一个n的别名
int& Count()
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();
return 0;
}
struct Array()
{
int a[10];
int size;
}AY;
//引用返回,1.减少了拷贝,返回了一个结构体的成员(出了作用域还在),
int& PosAt(AY &ay,int i)
{
assert(i<N);
return ay.a[i]; //出了作用域还在
}
int main()
{
AY ay;
PosAt(ay,1);
//2.调用者修改了这个返回值
for(int i = 0;i<N;i++)
{
count<<PosAt(ay,i)<<endl;
}
}
总结:如果函数返回时,出了作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统,则必须使用传值返回。
传值,传引用效率比较:
以值作为参数或者返回类型,在传参和返回期间,函数不会直接传递实参或者变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率很低,尤其当参数或者返回值类型非常大时,效率更低。
5.4引用和指针
1.在语法概念上,引用就是一个别名,没有独立空间,和其引用实体共用 一块空间,在底层实现上实际上是有空间的,因为引用是按照指针方式实现的。
2.引用是定义一个概念的别名,指针存储一个变量地址
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4.没有null引用,但有null指针
5.引用在定义的时候必须初始化,指针没有要求
6.在sizeof中的含义不同:引用结果为引用类型的大小,但是指针始终是地址空间所占字节个数(32位下4个字节,64位下8个字节)
7.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
8.有多级指针,但是没有多级引用
9.访问实体方式不同,指针需要显式解引用,引用编译器自己处理
10.引用比指针使用相对更安全
6.内联函数
以inline修饰的函数叫做内联函数,编译c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提高了程序的运行效率。
6.1内联的特性
1.inline是一种以空间换时间的做法,如果编译器将函数当做内联函数处理,在编译阶段,会以函数体替换函数调用,优点:少了调用开销,缺点:可能使目标文件变大
2.inline对于编译器只是一个建议,不同编译器关于inline实现机制不同,一般建议:将函数规模较小,不是递归,且频繁的调用采用inline修饰,否则会忽略inline特性。
3.inline不要声明和定义分离,分离会导致链接错误,因为inline被展开,没有函数地址了,链接就会找不到。
7.宏
#define N 10
int add(int a, int b)
{
...
}
#define Add x+y
#define Add ((x)+(y))
宏的优点:
1.增强代码的复用性
2.提高性能
宏的缺点:
1.不方便调试宏(在预编译阶段进行了替换)
2.导致代码的可读性差,可维护性差,容易误用
3.没有类型安全的检查
C++中可以替代宏:
1.定义常量 换用const,enum.
2.短小函数换位inline
8.auto关键字(c++11)
随着程序越来越复杂,程序中用到的类型也更复杂,经常体现在:
1.类型难于拼写
2.含义不明确导致错误
auto 关键字 可以自动推导出变量的类型。使用auto定义变量时,必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。
8.1 auto的使用细则
1.auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*相同,但是auto在声明引用类型时,必须加&
int x = 10; auto a = &x; auto* b =&x; auto&c = x;
2.在同一行定义多个变量
当在同一行声明多个变量的时候,这些变量必须是相同的类型,否则编译器会报错,因为编译器只对第一个类型进行推到,然后用推出来的类型定义其他变量
auto a = 1, b = 2; auto c = 3, d = 4.0 //err
3.auto不能推的场景
- auto不能作为函数的参数
- auto不能用来声明数组
9.基于范围的for循环(语法糖)
9.1范围for的语法
//常规的for循环
void Testfor()
{
int arr[] = {1,2,3,4,5};
for(int i =0; i<sizeof(arr)/sizeof(arr[0];++i)
arr[i] *=2;
for(int *p = arr ;p<array+sizeof(arr)/sizeof(arr[0]);++p)
cout<<*p<<endl;
}
对于一个有范围的结合,由程序员说明循环的范围是多余的,有时候还会出错,在c++11中引入了基于范围的for循环,for循环后的括号由冒号:分为两部分,第一部分是范围内用于迭代的变量,第二部分是表示被迭代的范围
int arr[] = {1,2,3,4,5};
for(auto& e:array)
e*=2;
for(auto e:array)
cout<<e<<endl;
9.2范围for的使用条件
1.for循环的迭代返回必须是已知的
2.迭代的对象要实现++和==的操作(后续迭代器中说明)
10.指针空值--nullptr(c++11)
NULL实际上是一个宏,表示0,有时候初始化为null和0会有歧义,所以新定义了一个关键字nullptr(关键字)
总结
本文主要讲了c++中的一些关键字和小知识点,能力有限,有错误请指正。