个人主页~
C语言是结构化和模块化的语言,适合处理小规模的程序,对于复杂的问题、规模较大的程序,需要高度的抽象和建模时,C语言则不合适,这时出现了面向对象的思想,支持面向对象的程序设计语言应运而生
C++基于C语言产生,是在C语言的基础上实现的,也就是说:C++兼容C语言的语法
java、c++都是面向对象的语言
我在网站上找到了C++的关键字,C++一共有63个关键字,其中包含着C语言的32个关键字(红色标识)
我们知道在C语言中,变量的名字是不能够相同的,否则不会编译通过,但是在全局定义变量时,我们可能会定义一个与包含头文件中某个变量名字相同的已经被定义过的变量、类型或函数
使用命名空间可以避免这种情况的发生,使程序更加明了
这里我们需要提到上方图中的关键字:namespace
namespace name { //...... }
namespace后面跟着命名空间的名字,可以任取,花括号中是命名空间的成员,可以有变量、函数、自定义类型以及嵌套命名空间
namespace slm { int rand = 1; int Add(int x, int y) { return x + y; } typedef struct sNode { int a; struct sNode* next; }sNode; namespace super { int rand = 2; } }
同一个工程中,允许存在多个相同的命名空间,编译器最后会合成到同一个命名空间中,也就是被相同命名的空间都在一个位置,就像是对于我们国家来说,其中的成员不管是山东省还是台湾省,都属于中国
int main() { printf("%d\n", slm::rand); return 0; }
using slm::Add; int main() { printf("%d\n", slm::rand); printf("%d\n", Add(1, 2)); return 0; }
#include namespace slm { int r = 1; int Add(int x, int y) { return x + y; } typedef struct sNode { int a; struct sNode* next; }sNode; namespace super { int r = 2; } } using namespace slm; int main() { printf("%d\n", r); printf("%d\n", Add(1, 2)); return 0; }
#include //std是标准库的命名空间名,C++中标准库的定义都放在这个命名空间中 using namespace std; int main() { cout << "HELLO WORLD" << endl; return 0; }
1、cout是标准输出对象,cin是标准输入对象
2、头文件iostream(为了与C语言头文件进行区分,以及更好地使用命名空间,C++头文件不带.h)
3、cout和cin是全局的流对象,endl相当于’\n’,是特殊的C++符号,表示换行输出
4、<<是流插入运算符,>>是流提取运算符
5、C++的输入输出可以自动识别变量类型
#include using namespace std; int main() { int a; double b; char c; //三个类型不同的变量 cin >> a; cin >> b >> c; //流提取,也就是相当于C语言中的scanf cout << a << endl; cout << b << " " << c << endl; //流插入,对于第二行来说,先插入b值,再插入空格,再插入c值,相当于C语言中的printf return 0; }
这里对于C++标准库的命名空间,使用using namespace std 可以全部展开,不全部展开也有别的使用方法:也就是std::
int main() { int a; double b; char c; std::cin >> a; std::cin >> b >> c; std::cout << a << std::endl; std::cout << b << " " << c << std::endl; return 0; }
或者
using std::cin; using std::cout; using std::endl; int main() { int a; double b; char c; cin >> a; cin >> b >> c; cout << a << endl; cout << b << " " << c << endl; return 0; }
缺省参数就是声明或定义函数时,为函数的参数指定一个缺省值,在调用函数时,如果没有实参则采用该形参的缺省值,如果有实参则采用实参
C语言不支持缺省参数
缺省值必须是常量或者全局变量
using namespace std; void func(int i = 0) { cout << i << endl; } int main() { func(); func(1); return 0; }
缺省参数不能在函数声明和定义中同时出现,通常我们把缺省参数放到头文件中,在源文件中就不能再次指定缺省参数
using namespace std; void func(int i = 0, int j = 1, int k = 2) { cout << i << " "; cout << j << " "; cout << k << " "; cout << endl; } int main() { func(); func(1); func(1, 2); func(1, 2, 3); return 0; }
半缺省的缺省值不是随便给的,只能是从右往左给,也就是说,一个非缺省参数的左边的参数不可能是缺省参数
using namespace std; void func(int i, int j = 1, int k = 2) { cout << i << " "; cout << j << " "; cout << k << " "; cout << endl; } int main() { //func(); func(1); func(1, 2); func(1, 2, 3); return 0; }
C++允许在同一个作用域中声明几个功能类似的同名函数,这些同名函数的参数个数、类型、类型顺序可以不同,用来处理实现功能类似但数据类型不同的问题
using namespace std; //参数类型不同 int Add(int x, int y) { return x + y; } double Add(double x, double y) { return x + y; } //参数个数不同 void func() { cout << "func()" << endl; } void func(int a) { cout << "func(int a)" << endl; } //参数类型顺序不同 void func(int x, char y) { cout << "func(int x, char y)" << endl; } void func(char y, int a) { cout << "func(char y, int a)" << endl; } int main() { cout << Add(10, 20) << endl; cout << Add(11.1, 22.2) << endl; func(); func(1); func(1, 'a'); func('a', 1); return 0; }
这里我们懂了chognzai函数重载的概念,其实cout可以自动识别数据类型也是一种重载
C++支持函数重载的原理是名字修饰
在C/C++中,一个程序需要经过预处理、编译、汇编、链接才能运行,每个项目由多个源文件构成,如果文件A中没有某函数的定义,但使用了,这样编译器会找不到函数的地址,另一文件B中有,在链接时,可以找到B中的函数地址,然后链接到一起,在链接时函数名会被修饰
因为Windows下的vs修饰规则比较复杂,我们选择在Linux下观察
结论:gcc的函数修饰后名字不变,而g++的函数修饰变成 _Z+函数长度+函数名+类型首字母
也就是说,C语言没法支持重载,同名函数没办法区分,C++可以通过名字修饰的方式形成不同名字的函数,只要参数不同,修饰出来的名字就不一样,就支持了重载
两个函数的函数名和参数相同不支持重载,调用时编译器没法区分
引用就是给已有的变量取别名,它和它引用的变量共用同一块内存空间
using namespace std; int main() { int a = 10; int& b = a; //数据类型+& 变量名 = 已定义的变量名; cout << a << endl; cout << b << endl; printf("%p\n", a); printf("%p\n", b); return 0; }
(1)在定义时必须初始化
(2)一个变量可以有多次引用
(3)一旦引用一个实体,就不能再引用其他实体
总的来说:引用要初始化,变量可以看成母本,引用可以看成子本,母本可以有多个子本,但子本只能有一个母本
using namespace std; int main() { int a = 10;//当a为变量时 //int& ra = a; (√)平移引用 //const int& ra = a; (√)缩小引用 //我们可以用const和非const来修饰int& const int a = 10;//当a为常量时 //int& ra = a;(×)方法引用 //const int& ra = a;(√)平移引用 //我们只能用const来修饰 return 0; }
把变量修饰成常量取消了他变化的能力,缩小了能力,在引用时,可以缩小引用,可以平移引用,但是不能放大引用
引用跟指针很像,C++老大在优化指针时搞出来的
void Swap(int& a, int& b) { int tmp = a; a = b; b = tmp; }
引用返回使用时要注意一个问题:如果函数返回时,出了函数作用域返回对象还在,则可以使用引用返回,如果已经回收系统,则必须使用传值返回
在函数栈帧的那篇博客中详细描述过,在函数调用结束后,函数的地址就没办法再访问到,如果没有函数再开辟栈帧,那么里面的内容还能够访问到,如果有,那么就会被覆盖
有关于函数栈帧的详细解说可以看这里~
所以引用返回所引用的值最好是全局变量或者高于函数局部变量的局部变量
在传递小的、简单的数据类型时,传值效率高
在传递大的、复杂的数据类型时,传引用或指针的效率更高
我在前面提到过引用和指针很像,其实它们的底层实现是一模一样的
int main() { int a = 0; int* pa = &a; int& ra = a; return 0; }
我们可以看到这里的反汇编代码是一模一样的
不同点:
类型 | 引用 | 指针 |
---|---|---|
概念 | 定义一个变量的别名 | 存储一个变量的地址 |
定义 | 必须初始化 | 没有要求 |
指向性 | 只能引用一个实体 | 可以指向任何同一类型的实体 |
NULL | 没有NULL引用 | 有NULL指针 |
在sizeof中的意义 | 引用类型的大小 | 地址空间所占字节个数 |
自加 | 引用自加就是被引用的实体加一 | 指针自加就是向后偏移一个类型的大小 |
多级 | 没有多级引用 | 有多级指针 |
访问实体方式 | 编译器自己处理 | 显示解引用 |
安全性 | 较安全 | 较危险 |
以关键字inline修饰的函数叫内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,提升程序运行的效率
(1)inline是一种空间换时间的做法,如果函数被当做是内联函数处理,在编译阶段会用函数体替换函数调用,它可能会使目标文件变大,但它减少了调用的开销,提高了程序的运行效率
(2)inline对于编译器来说并不是命令而是一种建议,一般来说,函数规模较小、不是递归且频繁调用的函数用inline修饰,否则会被编译器忽略
(3)inline不能够声明与定义分离,会导致链接错误,因为编译器需要看到内联函数的定义才能内联展开
C++中的内联函数对标C中的宏,宏的优点是增强代码的复用性以及提高性能,缺点为不方便调试、代码可读性可维护性差、没有类型安全的检查,所以对于常量定义来说,换用const和enum,对于短小函数的定义来说,C++提供的办法就是换用内联函数
//func.h #include using namespace std; inline void func(int i);//声明 //func.c #include "func.h" void func(int i) { cout << i << endl; }//定义 //test.c #include "func.h" int main() { func(1); return 0; }
结果显示链接错误,所以inline声明与定义要在一个头文件中
当程序越来越复杂,程序中所用的类型也会越来越复杂,以前我们会用typedef来取别名,但是typedef的局限性在于不能识别类型,所以auto关键字就出现了
老标准:使用auto修饰的变量,是具有自动存储器的局部变量,但局部变量默认就是自动存储器,所以没人使用
C++11的新标准:在编译时期自动类型推导
这里我们介绍一个运算符typeid,用于在运行时获取类型信息
using namespace std; int TestAuto() { return 1; } int main() { int a = 10; auto b = a; auto c = 'a'; auto d = TestAuto(); //auto自动对齐右边的类型 cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(d).name() << endl; //检测bcd的类型 return 0; }
使用auto定义变量时必须对其进行初始化,在编译阶段根据初始化表达式来推到auto的实际类型,然后将其替换
用auto声明指针类型时,用auto和auto*没有任何区别,但auto声明引用类型时必须加&
using namespace std; int main() { int x = 10; auto a = &x; auto* b = &x; auto& c = x; cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; return 0; }
这些变量必须是相同的类型,编译器只会对第一个类型进行推导,然后用推导出来的类型定义其他的类型
auto不能作为函数的参数
auto不能直接用来声明数组
我们以前使用for循环需要这么用:
using namespace std; void TestFor() { int array[] = { 1, 2, 3, 4, 5 }; for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p) cout << *p << " "; cout << endl; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) array[i] *= 2; for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p) cout << *p << " "; }
对于范围for来说,for循环后的括号由冒号分成两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
并且它跟普通for循环类似的是,可以用continue结束此次循环和break退出循环
void TestFor() { int array[] = { 1, 2, 3, 4, 5 }; for (auto e : array) cout << e << " "; cout << endl; //这里e只是一份复制品,需要引用才能修改array当中的值 for (auto& e : array) e *= 2; for (auto e : array) cout << e << " "; }
(1)范围for循环迭代的范围必须确定,对于数组来说,就是第一个元素到最后一个元素
(2)迭代的对象要实现++和==的操作
指针空值我们以前使用NULL,但在C++中,NULL是被赋予值的,为0,在C中,NULL就是(void*)0,所以我们产生了新的指针空值nullptr,在C++中代表着(void*)0
在使用nullptr时不需要引入其他头文件
在C++11中,sizeof(nullptr) == sizeof((void * )0)
在C++中表示指针空值时最好使用nullptr
今日分享结束~