目录
C++关键字:
C语言有32个关键字(c90),C++有63个关键字(c++98)。
命名空间:
定义:
表示一个标识符的可见范围,使用namespace{ }的形式定义。
注意:
1、命名空间中的内容,即可以定义变量,也可以定义函数。
2、命名空间可以嵌套使用。
3、同一个工程中允许存在多个相同名称的命名空间,编译器最终会合成同一个命名空间中(所以相同名称的命名空间中不能存在相同的成员变量名称额,否则会产生冲突)
4、一个命名空间就定义了一个新的作用域,命名空间中所有的内容都局限于该命名空间中。
namespace N1{
int a = 1;
int add(int left, int right) {
return left + right;
}
namespace N2 {
int b = 0;
}
}
命名空间中成员的使用:
1、加命名空间名称及作用域限定符(空间名称:: 变量名)
特点:每次使用成员变量的时候都要这样写
namespace N1{
int a = 1;
int add(int left, int right) {
return left + right;
}
namespace N2 {
int b = 0;
}
}
int main() {
printf("%d\n", N1::add(1, 3));
system("pause");
return 0;
}
2、使用using将命名空间中的成员引入
namespace N1{
int a = 1;
int add(int left, int right) {
return left + right;
}
namespace N2 {
int b = 0;
}
}
using N1::add;
int main() {
printf("%d\n", add(1, 3));
system("pause");
return 0;
}
特点:后续使用该变量可以不加任何说明,直接使用。
3、using namespace 空间名称
特点:凡是该空间的成员变量,均不需要任何说明,可以直接使用。
namespace N1{
int a = 1;
int add(int left, int right) {
return left + right;
}
namespace N2 {
int b = 0;
}
}
//using N1::add;
using namespace N1;
int main() {
printf("%d\n", add(a, 3));
system("pause");
return 0;
}
在日常练习中使用3,在项目中使用1或者2,其目的是为了避免发生冲突。
输入输出
using namespace std;
int main() {
//printf("%d\n", add(a, 3));
cout << "hello world"<<endl;
system("pause");
return 0;
}
注意:使用cout和cin的时候必须包含头文件和标准命名空间std。
建议:再写项目的时候,建议用到std里面的那个对象,就单独引入这个对象,目的是避免引起不必要的冲突!
缺省参数
概念:
声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
using namespace std;
void func(int a = 10) {
cout << a << endl;
}
int main() {
//printf("%d\n", add(a, 3));
func(); // 10
func(1); // 1
system("pause");
return 0;
}
全缺省参数:函数的所有参数都给定了一个默认值。
半缺省参数:参数中有部分参数给定默认值,并且是从右向左依次规定默认值。
注意:半缺省参数必须从右向左依次给定默认值。缺省参数不能在函数声明和定义处同时出现,一般在声明处给定默认值。缺省值必须是常量或者全局变量。
函数重载
概念:
在同一作用域中有几个功能类似的同名函数,它们的形参列表(个数不用,顺序不同,类型不同)不同,这样的一组函数称为函数重载。
作用:
处理实现功能类似数据类型不同的问题。
为什么c++支持函数重载,而c语言不支持函数重载?
在C/C++中,一个程序要运行起来,需要经历以下四个阶段:
1、预处理:主要进行头文件展开、宏替换、条件编译、去注释
2、编译:将预处理结束后的代码转换为汇编程序
3、汇编:将汇编程序转换为二进制代码
4、链接:将生成的二进制代码与库函数以及其他目标文件通过连接器链接,最终生成可执行程序。
在链接过程中,遇到函数调用时会通过查看符号表到对应的库或者时其他声明该函数的地方去找。 此时,连接器会使用哪个名字去找?每个编译器都有自己的函数名称修饰规则。
linux下的gcc和g++:
gcc函数修饰后名字不变;而g++的函数修饰后函数名字发生改变,变成【_Z + 函数长度 + 类型首字母】。
Windows下:
c程序编译后函数修饰名字不变。c++程序对函数名字的修饰则非常复杂,但是其目的是一样的。
总结:c语言无法支持函数重载,是因为函数名无法进行区分;c++是通过函数修饰规则来区分 ,只要参数不同,修饰出来的名字就不一样,所以支持函数重载。
重载过程:
①编译器拿用户传入的参数与已经定义的函数参数对比
②如果参数对应完全一致,直接调用该函数
③若没有参数完全一致的重载函数,则默认的对用户的参数进行隐式类型转化
④转化之后若有适合的重载函数,编译通过。否则直接报错!
extern “C”:
有时候c++工程中可能需要将某些函数按照c的风格来编译。在函数前加extern “C”是告诉编译器,将该函数按照C语言的风格来编译。
注意:
在C++程序中,使用extern “C”来修饰某一个函数,其本质是告诉编译器在生成符号表时告诉编译器是对该函数不采用C++的编译规则,而是采用C语言的编译规则。
被extern "C"修饰的函数,在符号表中的名字就是该函数的函数名,不做任何修饰。被extern “C”修饰的函数,不能有函数重载。
引用
概念:
引用不是定义一个新的变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
int main() {
int a = 1;
int& ra = a;
cout << a << endl;
cout << ra << endl;
cout << &a << endl;
cout << &a << endl;
system("pause");
}
特性:
1、引用在定义的时候必须初始化
2、一个变量可以有多个引用
3、引用一旦引用一个实体,再不能引用其他实体
常引用:
int main() {
const int a = 10;
//int& ra = a; 编译时会出错,因为此时a为一个常量
const int& ra = a;
//int& b = 2; 编译时会出错,b为常量
const int& b = 2;
double c = 3.14;
//int& rc = c; 编译出错
const int& rc = c; //rc并不是c的别名,而是而是该过程产生的临时变量的别名
//在此过程中会创建一个int类型的临时变量,临时变量具有常性,因此加上const后就不会报错
}
引用的使用场景
1、做参数
void swap(int& left, int& right) {
int temp;
temp = left;
left = right;
right = temp;
}
int main() {
int a = 10;
int b = 20;
swap(a, b);
cout << a << endl;
cout << b << endl;
system("pause");
}
2、做返回值
int& Count()
{
static int n = 0;
n++;
return n;
}
int main() {
cout << Count() << endl;
system("pause");
}
注意:
如果以引用类型作为函数的返回值类型:返回的实体的声明周期一定要比函数长,既不能随函数的结束而销毁(此时引用的对象的地址已经交还给了操作系统)
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给了系统,则必须使用传值返回。
传值和传引用的效率:
以值作为参数或者返回值类型,在传参和返回值期间,函数不会直接传递实参或者将变量直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型 效率十分低下的,尤其是当参数或者返回值类型非常大的时候,效率就更低了。
引用和指针的区别:
在语法概念上,引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
但在底层实现上引用其实是有空间的,因为引用时按照指针方式来实现的。
下面的代码可以验证:
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
可以看出引用和指针的汇编代码时一摸一样的。
①int&可以看成是一个int* const类型的指针(该指针的指向不能变)
②const int& 可以看做const int* const(即指针的指向不能变并且指针指向的内容不能变)
引用和指针的不同点:
1、引用在定义时必须初始化,指针没有要求。
2、引用在初始化时引用了一个实体后,就不能再引用其他实体,而指针可以在任何时候指向一个同类型的实体。
3、没有NULL但是有NULL指针
4、在sizeof中含义不同,引用结果为引用 类型的大小,但指针始终时地址空间所占字节个数(32位平台下4字节,64位平台下8字节)
5、引用自增表示对引用的实体+1,指针自增表示指针向后偏移一个类型的大小。
6、有多级指针,但是没有多级引用。
7、访问实体方式不同,指针需要显式解引用,引用编译器会自己处理
8、引用比指针使用起来相对较为安全
内联函数
1、概念:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序的运行效率。
如果在上述函数前加上inline关键字将其改为内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:
1、在release模式下,查看编译器生成的汇编代码中是否存在call Add
2、在debug模式下,需要对编译i进行设置,否则不会展开。因为debug模式下,编译器默认不会对代码进行优化,下面是结局方案。
2、特性
①inline是一种以空间换时间的做法,省去调用函数的额外开销。所以代码很长或者有循环、递归的函数不适宜做内联函数。
②inline对于编译器只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环递归等,编译器优化时会忽略掉内联。
③inline不建议定义和声明分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
宏的优缺点
1、宏常量
优点:一改全改;降低出错率;可读性高
缺点:不方便调试宏,因为预处理阶段进行了宏替换;如果没有类型安全的检查,安全性低,如果出错,在预处理阶段不会在宏定义处报错,而是会在使用处报错。这样会给查找错误来源造成不便。
综上所述,建议使用C++中的const常量代替宏常量。
2、宏函数
优点:并不是真正意义上的函数,不会有函数调用,提高程序的运行效率;少些代码,因为宏函数是多条语句的封装(不能提高代码的复用率,因为宏函数在预处理阶段就展开了);提高代码的可读性。
缺点:在预处理阶段被替换,不会进行类型检测,代码安全性低;不能调试;每个部分都会展开,造成代码膨胀;容易出错,每个部分都要加括号(即使加了括号,在有些特殊场景下也会出错);宏函数可能会有副作用。
建议:使用内敛函数代替宏函数(函数的写法,宏的作用)
C++有哪些技术可以代替宏?
1、常量定义换用const
存在数据类型,有对数据类型的检查,可以调试;编译器会对const类型常量进行安全性检查,而不是简单宏替换。
2、函数定义换用内联函数
①内联函数是函数,有参数类型,会在编译阶段进行参数的检测,代码安全性高
②在debug模式下不会展开,可以调试
③实现简单,不要向宏函数需要到处加括号,可读性高
④在编译的阶段展开,少了调用的函数开销(建议性的,具体看编译器)。
auto关键字 (C++11)
简介:
在早期C/C++中auto的含义是:使用auto修饰的变量是具有自动存储的局部变量
C++11中,标准委员会赋予了auto全新的含义:auto不再是一个存储类型指示符,而是作为一个全新的类型指示符来指示编辑器,auto声明的变量必须由编译器在编译时期推导而得。
如下:
注意:使用auto定义变量的时候必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种类型的说明,而是一个类型额声明时的“占位符”,编译器在编译阶段会将auto替换为变量实际的类型。
使用规则:
1、用auto声明指针类型时,用auto和auto*没有任何区别,但auto声明引用类型时必须加&
2、在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3、auto不能作为函数的参数
4、auto不能直接用来声明数组
5、为了避免与C++98中的auto发生混淆,C++11只保留了作为类型指示符的用法
6、auto在实际中最常见的用法就是跟C++11提供的新式for循环,还有lambda表达式
基于范围的for循环(C++)
对于一个有范围的集合而言,由程序员说明循环的范围是多余的,有时候还容易犯错误。因此C++11中引入了基于范围的for循环的括号由‘:’分为两部分,第一部分时范围内用于迭代的变量,第二部分则表示被迭代的范围:
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
for循环迭代的范围必须时确定的:对于数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
空指针nullptr(C++11)
NULL实际是一个宏,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值指针时,都不可避免会遇到一些麻烦。
在C++98中,字面常量0既可以是一个整形数字,也可以时无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整型常量,如果将其按照指针方式来使用,必须对其进行强转(void*)0。
注意:
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr时C++11作为新关键字引入的。
2、在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同。
3、为了提高代码的健壮性,在后续表示指针空值时最好使用nullptr