赞
踩
目录
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,
20世纪80年代, 计算机界提出了OOP(objectoriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。
1982年,BjarneStroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
此刻,以熟悉的hello world 开启c++的学习
#include<iostream> using namespace std; int main() { cout << "hello world" << endl; return 0; }
在这里我们可以先了解一下
或许很多人都知道在编写一个cpp程序时要加入 using namespace std ;但你知道这究竟是为什么嘛?
———在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
namespace定义的是一个域,本质是解决C语言命名冲突的问题
eg:
针对以上程序我们的期望是,19行的scanf调用的是函数scanf,其他的scanf调用的是我们定义的变量,但是程序调用时遵循的是就近原则(从局部域开始找),19行的scanf调用不会去<stduio.h>中调用函数,而是调用了离它最近的scanf变量(16行),所以就产生了命名冲突的问题,C语言是解决不了这个问题的。
如果将他俩放到全局,就会出现重定义的问题
eg:
通过namespace就可以完美解决问题
1. 定义命名空间首先用关键字namespace,在关键字后面加上名称,再加一对{}即可
- namespace n1 //n1 为命名空间的名称
- {
- int a = 0; //命名空间中可以定义函数,也可定以变量
- int add(int a, int b)
- {
- return a + b;
- }
- }
2. 命名空间可以嵌套
- namespace n2
- {
- namespace n3
- {
- namespace n4
- {
- //...
- }
-
- }
- }
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
- namespace n1
- {
- int Mul(int left, int right)
- {
- return left * right;
- }
- }
注意:一个命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间当中
C++库为了防止命名冲突,把自己库里面的的东西都定义在一个std的命名空间中去,要使用标准库中的东西,有三种方式
ps:#include<iostream.h> 老一点的C++标准用这个,VC6.0 可以用,没有std命名空间
1.0 指定命名空间 ,麻烦,每个地方要用都要指定,但它是最规范的方式
2.0 吧std整个展开,相当于库里面的东西都到全局域里面去了,看起来方便了,如果我们自己定义的东西跟库起冲突了,就没法解决。一夜回到解放前,所以规范的工程项目中是不推荐这种方式的,日常练习无所谓
3.0 对部分常用的库里面的东西展开
PS: :: 这是域作用限定符,C语言调用全局变量可以用
定义了如下的空间N1 ,使用它一共有三种使用方式
- #include <iostream>
- namespace N
- {
- int a = 10;
- int b = 20;
- int add(int left, int right)
- {
- return left + right;
- }
- int sub(int left, int right)
- {
- return left - right;
- }
- }
-
- int main()
- {
- //printf("%d", a);//直接使用编译器会报错
- }
1.0 加命名空间名称及作用域限定符 什么时候用什么时候打开空间,安全,但是较为复杂
int main() { printf("%d ", N::a); printf("%d ", N::b); }
2.0 使用using namespace 命名空间名称引入,不安全但是简单
using namespace N; int main() { printf("%d ", a); printf("%d ", b); int c=add(1, 2); printf("%d ", c); int d=sub(4, 3); printf("%d ", d); }
3.0 使用using将命名空间中成员引入 ,用哪个,提前打开哪个对象安全,推荐使用
using N::b; int main() { printf("%d ", N::a); printf("%d ", b); }
想必到达这里就会明白为什么要用using namespace std;这一串话了(这段话告诉编译器我们要用名字空间std中的函数或者对象)
但是日后当我们在写成熟的代码的时候,一般不建议将标准命名空间全部打开,而是需要用库里的什么就打开什么。这就有效的防止了命名冲突
例如写一个hello world
第一种就是上面提到的
使用using namespace 命名空间名称引入,不安全但是简单
#include <iostream> using namespace std; int main() { cout << "hello,world" << endl; return 0; }第二种,用哪个,提前打开那个对象
安全,推荐使用
#include <iostream> using std::cout; using std::endl; int mian() { cout << "hello world" << endl; }第三种,什么时候用什么时候打开std
安全,但是较为复杂
#include <iostream> int main() { std::cout << "hello world" << std::endl; }
ostream 类型全局对象 cout
istream 类型全局对象 cin
endl 全局换行符号
又是老朋友:hello world
- #include <iostream>
- using namespace std;
- int main()
- {
- cout << "hello,world" << endl;
- return 0;
- }
对比C语言的scanf 与 printf ,C++的cout与cin的区别是什么
cout与cin可以自动识别类型
eg:
但cin,cout也不是在任何情况下都好用,
eg:
- #include<iostream>
- using namespace std;
- struct Person
- {
- char name[10];
- int age;
- };
-
- int main()
- {
- int a = 0;
- cin >> a; //scanf("%d ",&a);
- cout << a << endl;
- //这里cin就比scanf好很多
-
- struct Person p = { "小王",10 };
- printf("name:%s age:%d\n", p.name, p.age);//格式化输出printf更好一点
- cout << "name: " << p.name << "\n" << "age:" << p.age << endl;
- return 0;
-
- }
综合而言,两者可以混着用,谁好用谁。
2. 使用C++输入输出更方便,不需增加数据格式控制,比如:整形--%d,字符--%c
- #include <iostream>
- using namespace std;
- int main()
- {
- int a;
- double b;
- char c;
- cin >> a; // >> 流提取操作符
- cin >> b >> c;
- cout << a << " " << b << " " << c << endl; // << 流插入操作符
- return 0;
- }
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。缺省参数又分为全缺省与半缺省。
说白了缺省函数就是,C++中函数的参数中配的备胎
1.0全缺省,全部的参数都给了一个缺省值
#include<iostream> using namespace std; void fun(int a = 10, int b = 20, int c = 30) { cout << "a= " << a << endl; cout << "b= " << b << endl; cout << "c= " << c << endl << endl; } int main() { fun(); //不传参就用默认的 fun(1, 2, 3); //传了就用传了的 fun(1); //传了部分 fun(1, 2); //传了部分 }执行结果:
2.0 半缺省参数,部分的参数给了缺省值
#include<iostream> using namespace std; void fun2(int a, int b, int c = 30) { cout << "a= " << a << endl; cout << "b= " << b << endl; cout << "c= " << c << endl << endl; } int main() { fun2(1, 2); // 没传就用默认值 fun2(1, 2, 3); // 传了就用传了的 }执行结果:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
正确的半缺省函数写法:错误的半缺省函数写法:
原因也很简单,因为函数传参的时候是从左向右依次传参的
2. 缺省参数不能在函数的声明和函数的定义中同时出现
注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
//a.h void TestFunc(int a = 10); // a.c void TestFunc(int a = 20) {}
3.缺省值必须是常量或者全局变量
4.C语言不支持(编译器不支持)
缺省函数的作用就是为了使函数调用更加灵活,乍一听感觉好像也没啥用,但在一些情况下发挥着巨大的作用;
eg:我在这写了个栈及栈的初始化,初始化我写的是开四个空间,在主函数中,假设我知道栈里面至少要存100个数据,这个初始化显然不合理只能开4个,接着就开始增容,增到100,同时增容是要付出代价的,有消耗的,所以就很不方便。但假设我知道这个栈里面最多存10个数据,而对这个栈来说,我们写的初始化就很合理,增容的消耗也小。所以就造成了不方便的局面。
struct Stack { int* a; int size; int capacity; }; void StackInit(struct Stack* ps) { ps->a = (int*)malloc(sizeof(int) * 4); ps->size = 0; ps->capacity = 0; } int main() { struct Stack st1; //假设我知道栈里面至少要存100个数据 StackInit(&st1); //这个初始化显然不合理,接着就开始增容,增到100,同时增容是要付出代价的,有消耗的 struct Stack st2; //假设我知道这个栈里面最多存10个数据 StackInit(&st2); //而对这个栈来说,我们写的初始化就很合理,增容的消耗也小 return 0; }
这时候缺省参数的作用就体现出来了,我们将初始化的函数多传一个缺省值4
void StackInit(struct Stack* ps, int InitCapacity = 4) { ps->a = (int*)malloc(sizeof(int) * InitCapacity); ps->size = 0; ps->capacity = InitCapacity; }假设我知道栈里面至少要存100个数据,缺省值就传100。
假设我知道这个栈里面最多存10个数据缺省值就传10。
假设我不知道这个栈里面可能存多少数据,缺省值就啥也不传,非常方便的解决了问题,同时体现出缺省函数的作用:使函数调用更加灵活。
int main() { struct Stack st1; //假设我知道栈里面至少要存100个数据 StackInit(&st1,100); struct Stack st2; //假设我知道这个栈里面最多存10个数据 StackInit(&st2,10); struct Stack st3; //假设我不知道这个栈里面可能存多少数据 StackInit(&st2); return 0; }
函数重载的概念:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。
C语言不允许定义同名函数,C++可以,要求就是函数的(参数个数 或 类型 或 顺序)必须不同。
eg:下列函数都够成重载
#include<iostream> using namespace std; int Add(int left, int right) { return left + right; } char Add(char left, char right) { return left + right; } double Add(double left, double right) { return left + right; } long Add(long left, long right) { return left + right; } int main() { cout << Add(10, 20) << endl; //匹配int型 cout << Add(10.0, 20.0) << endl; //匹配double型 cout << Add(10L, 20L) << endl; //匹配long型 cout << Add('1', '2') << endl; //匹配char型 return 0; }结果如下:
这两个函数构成重载吗?
构成重载,但是不能同时调用,因为编译器识别不了到底应该调用谁
注意:这两个函数就不构成运算符重载,因为参数的类型,个数都一样,只是返回值不同
short Add(short left, short right) { return left+right; } int Add(short left, short right) { return left+right; }
下面两个函数构成重载吗?
void TestFunc(int a = 10) { cout<<"void TestFunc(int)"<<endl; } void TestFunc(int a) { cout<<"void TestFunc(int)"<<endl; }答案:不构成,他俩类型相同,只是缺省参数不同`
1.0为什么C语言不支持重载,C++支持重载?C++底层是如何支持重载的
以这单代码为例
编译链接的过程
第一步 预处理---头文件展开+宏替换+去掉注释+条件编译--func.i test.i
第二步 编译--检查语法,生成汇编代码--func.s test.s
第三步 汇编--把汇编代码转成二进制机器代码--func.o test.o
第四步 链接--把.o文件链接到一起,合并符号表,生成可执行程序--a.out
C语言不支持函数重载,因为编译的时候,两个函数重载,函数名相同,在func.o符号表中存在歧义和冲突,其次在链接的时候也存在歧义和冲突,因为他们都是直接通过使用函数名去标识和查找。而重载函数,函数名相同。
C++的目标文件符号表中不是直接用函数名来标识和查找函数。它引入了函数名修饰规则(不同编译器下不同)。
g++的函数名修饰规则_Z+函数名长度+函数名+参数首字母
有了函数名修饰规则,只要参数不同,func.o符号表里面重载的函数就不存在二义性和冲突了。链接的时候,test.o的mian的函数里面去调用两个重载的函数,查找地址时也是明确的。
ps:main函数中调用函数时,如果当前文件有函数的定义,那么编译的时候就填上地址了;如果当前文件只有函数的声明,那么定义就在其他.cpp中,编译时没有地址,只能在链接的时候去其他.o符号表中根据函数修饰名字去找,这就是链接的重要工作。
实际场景中可能出现两种情况
1.有个东西是C++写的,现在有个C语言的程序,需要调用这个东西
2.有个东西是C写的,现在有个C++的程序,需要调用这个东西
引用的概念:引用不是定义一个新的变量,**而是给已知的变量取一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的的变量共用同一块内存空间。 (通俗来说,引用就是取别名)
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风”。
int main() { int a = 10; cout << "a:" << a << endl; //&在类型后面,就是引用的意思 int& b = a; // b就是a的引用(别名) b = 20; cout << "a:" << a << endl; int& c = b; //c就是b的引用 }通过监视我们可以看出完全符合引用的概念。
1.0 我们最熟悉的也就是交换两个值
- void swap(int* a, int* b) //C语言中的交换需要指针(形参的改变不影响实参的改变)
- {
- int tmp = *a;
- *a = *b;
- *b = tmp;
- }
-
- void swap(int& a, int& b) //C++的交换,用引用即可
- {
- int r = a;
- a = b;
- b = r;
-
- }
- int main()
- {
- int a = 10;
- int b = 20;
- swap(&a, &b); //C语言传地址
- swap(a, b); //C++传引用
- }
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
int main() { //一个变量可以有多个别名 int a = 0; int& b = a; int& c = b; int& d = c; }
3. 引用一旦引用一个实体,再不能引用其他实体
对于指针:
由监视可以看出,p的指向发生了改变
对于引用
由监视可知,开始引用了a,就只能引用a,不能去引用x。
首先明白const关键字 ,const修饰默一遍量就代表该变量不能修改了
const修饰代表a不能修改
int& ra = a; 错误原因: ra引用a属于权限的放大,a是可读的,ra却是可读可写的,所以不行
const int& ra = a;正确原因: 加上const后ra也变成只能读了,和原先的a权限保持一致,所以正确
const int& crb = b;//正确原因,crb引用b属于权限的缩小,b是可读可写的,crb是可读的,权限缩小,所以可行
首先明白
上述转换是可以的,发生了隐式类型转换,首先产生一个中间临时变量,这个中间临时变量是double类型的,将c赋给临时变量,再将临时变量赋给d
double& rc = c; 很多人说这种写法错误的原因是:rc是double类型,c是int类型的,这种解释是错误的。 而我们发现加了个const后就可以编过了,原因:发生类型赋值过程当中,将int赋给double,中间就会产生临时变量,临时变量是double类型,所以rc引用的是这个临时变量,而这个临时变量具有常性(只是可读的),所以加了const后编译通过,类似于eg1。
对这种传值,传a与c都是没有问题的
如果 假设x是一个大对象或者说是深拷贝的对象,那么尽量用引用传参,减少拷贝,但这时候a就属于权限的放大,所以程序编译不过
加入const就可以解决,如果f函数中不改变x,建议尽量用const引用传参
C语言中交换需要传地址,因为形参是实参的拷贝
void Swap(int* left, int* right) { int temp = *left; *left = *right; *right = temp; }C++中直接传引用就可以实现交换,既没有传值也没有传地址,传的是引用
void Swap(int& left, int& right) { int temp = left; left = right; right =temp; }同样,他俩也构成重载,但是调用swap的时候会存在歧义,编译器不知道该调用谁
- #include <time.h>
- struct A { int a[10000]; };
- void TestFunc1(A a) {}
- void TestFunc2(A& a) {}
- void TestRefAndValue()
- {
- A a;
- // 以值作为函数参数
- size_t begin1 = clock();
- for (size_t i = 0; i < 10000; ++i)
- TestFunc1(a);
- size_t end1 = clock();
- // 以引用作为函数参数
- size_t begin2 = clock();
- for (size_t i = 0; i < 10000; ++i)
- TestFunc2(a);
- size_t end2 = clock();
- // 分别计算两个函数运行结束后的时间
- cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
- cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
- }
- int main()
- {
- TestRefAndValue();
- }
ps:使用引用传参,如果函数中不改变参数的值,建议使用const Type & ,因为它可以接受各种类型的对象
eg:
void StackPrint(const struct Stack& st) {}
传值返回时(所有的传值返回都会生成一个拷贝),调用Add完成后会产生int类型的临时变量,c给了临时变量,临时变量再给给ret
返回的对象是c的拷贝通过下图我们可以证明确实产生了临时变量(ret引用的不是c,其实是这个临时变量,临时变量具有常性,所以第二个引用编过了)
为什么会产生临时变量呢?
如果直接把c给给ret会出现很多问题,函数Add执行完成以后,函数栈帧就销毁了,再去取c的值给给ret就会出现很多问题,有可能返回3,也有可能返回随机值,这取决于栈帧销毁的时候清不清理空间,严格意义上来说都是非法访问的,一块空间已经还给系统(销毁),我们就不能去访问了 ,所以说中间就会产生一个临时变量
这个临时变量存在哪呢?
1.0 如果c比较小(4个字节或者8个字节),一般是寄存器充当临时变量。
2.0如果c比较大,临时变量放在调用Add函数的栈帧中去
传引用返回 ,意思就是不会生成c的拷贝返回,直接返回c的引用
主函数中并没有加const编译通过,所以证明返回的是c的引用
这段代码看着是没问题,编译器也通过了,但它实际上却存在着问题:
1.0 存在非法访问,因为Add(1,2)的返回值是c的引用,所以Add栈帧销毁以后,回去访问c位置的空间
2.0 如果Add函数栈帧销毁时,清理空间,那么取c值的时候取到的就是随机值,给ret就是随机值 ,这个取决于编译器的实现。ps:vs下销毁栈帧是没有清理空间数据的
编译器有时候也会清理空间数据,例如对malloc函数
所以现在的用法是一个错误的用法。
那么如果所有的编译器在栈帧销毁时都不清理空间,那是不是就可以这样用了?
答案也是不行的。
这个时候ret就是c的引用(别名),就是c。为什么ret是3,最终会变成30呢?
第一次调用Add函数建立栈帧,调用完成后销毁,此时ret也就是c的值是3,然后再调用一次Add函数两次函数调用的栈帧大小是一样的,覆盖了同样位置的空间,c变成是30,函数栈帧销毁,再次打印ret,ret就是c,变成了30
PS:不同的栈帧不一定在同一块空间,原来的空间已经销毁了,在调用函数的时候就可以继续用之前的
cout同样也会建立栈帧,每个栈帧的大小取决于这个函数里面会开辟多少,这个函数里面的局部变量和参数越多,栈帧就越大,cout同样也是一个函数,他为什么没有影响ret,就说明cout的栈帧开的很小,没有覆盖到c,如果覆盖到c,那么c就会改变
eg:
这就说明printf开的栈帧将c给覆盖掉了
栈帧是调用完了就销毁掉,销毁掉以后,别人调用这可以利用这块栈帧,没对c覆盖就没影响,对c覆盖了就有影响
注意:一个函数调用就会向下空间建立一个栈帧,函数调用结束,栈帧就会销毁
引用返回的使用情况
实际过程中,出了函数作用域,返回对象就不存在了,不能用引用返回,但是加了static保证他出了作用域不会被销毁,就可以用引用返回了
总结下:如果函数返回时,出了函数作用域,如果返回对象还未还给系统(也就是没销毁,例如加了static),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是 传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是 当参数或者返回值类型非常大时,效率就更低。
#include <time.h> struct A { int a[10000]; }; A a; // 值返回 ---每次拷贝40000字节 A TestFunc1() { return a; } // 引用返回 ---没有拷贝 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作为函数的返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); }调用一万次比较他们的性能,结果如下
由此发现,引用返回比值返回快很多
1.0 引用传参和传返回值,在有些场景下,可以提高性能(当参数和返回值是比较大的变量时,传引用传参和传引用做返回值可以提高效率,深拷贝...)2.0 引用传参和传返回值,输出型参数和输出型返回值。通俗来说,在有些场景下,形参的改变可以改变实参,有些场景下,引用返回,可以改变返回对象。
- int main()
- {
- int a = 10;
- //在语法上,这里给a这块空间取了一块别名,没有开辟新空间
- int& ra = a;
- ra = 20;
- //在语法上,这里定义个指针变量,开辟了4个字节,存储a的地址
- int* pa = &a;
- *pa = 20;
- }
从汇编来看,引用的底层也是类似指针存地址的方式进行的
1. 引用在定义时必须初始化,指针最好初始化,但是不初始化也不会报错。
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 没有NULL引用,但有NULL指针
4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全
9.引用在概念上定义一个变量的别名(没开空间),指针存储一个变量的地址
调用函数需要建立栈帧,建立栈帧要保存一些寄存器,结束后又要恢复...,这些都是有消耗的,对这些频繁调用的函数如何进行优化呢?
c++频繁调用的函数,定义成inline,会在调用的地方展开,没有栈帧的开销
c语言为了小函数避免建立栈帧的消耗-》》提供了宏函数支持,预处理阶段展开
提到宏,我们进行一个小测试,请读者自己写一个Add的宏,先不要看答案。
普遍存在的错误写法
#define Add(int x,int y) return x+y;//典型的错误写法,是宏而不是函数
#define Add(x,y) x+y; //错误 分号问题
#define Add(x,y) x+y // 错误 优先级问题 #define Add(x,y) (x+y) // 错误 +的优先级大于|和&标准写法
记住宏原理是替换,你替换一下看看对不对
#define Add(x,y) ((x)+(y))
既然c语言已经解决了,为什么c++还要提供inline函数呢?(宏函数的缺点)
a.不支持调试 b.宏函数语法复杂容易出错 c.没有类型安全的检查
- inline int add(int x,int y) //直接在函数前加一个inline构成inline函数
- {
- return x + y;
- }
- int main()
- {
- int c = 0;
-
- }
1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长(一般来说10行以上就算长)或者有循环/递归的函数不适宜使用作为内联函数,因为展开以后程序会变的很大。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。4.类里面定义的函数默认就是inline函数。
这里解释以下什么叫以空间换时间
- int a = 10;
- auto b = a;//类型声明成auto,可以根据a的类型自动推导b的类型,a是int类型,b也是int类型
eg:
- int TestAuto()
- {
- return 10;
- }
- int main()
- {
- int a = 10;
- auto b = a;
- auto c = 'a';
- auto d = TestAuto();
-
- //typeid打印变量的类型
- cout << typeid(b).name() << endl;
- cout << typeid(c).name() << endl;
- cout << typeid(d).name() << endl;
-
- //auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
- return 0;
- }
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
- int main()
- {
- int x = 10;
- auto a = &x;
- auto* b = &x;
- int& y = x;
- auto c = y;
- auto& d = x; //指定了d是x的引用
-
- //typeid打印变量的类型
- cout << typeid(x).name() << endl;
- cout << typeid(a).name() << endl;
- cout << typeid(b).name() << endl;
- cout << typeid(y).name() << endl;
- cout << typeid(c).name() << endl;
- }
2. 在同一行定义多个变量
1.auto不能作为函数的参数
此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
2.auto不能用来推导数组
在c++98中如果要遍历一个数组,可以按照以下方式进行
- int arry[] = { 1,2,3,4,8,9 };
- for (int i = 0; i < sizeof(arry) / sizeof(int); i++)
- {
- cout << arry[i] << " ";
- }
为了简化操作,C++11引入了基于范围的for循环 for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
- for (auto e : arry)
- {
- cout << e << " ";
- }
同样也可以自己定义,但不如auto好用
-
- for (int x : arry)
- {
- cout << x << " ";
- }
如果我们想用范围for来改变数组中的值(让数组中的每一个值都加1),我们该如何做呢?
首先我们可能会简单的认为只要将e++即可
- int main()
- {
- int arry[] = { 1,2,3,4,8,9 };
-
- for (auto e : arry)
- {
- e++;
- }
-
- for (auto e : arry)
- {
- cout << e << " ";
- }
-
- }
但通过结果发现,数组并没有被改变,原因就是:这个范围for 是自动遍历,依次取出arry中的元素,赋值给e,直至结束,e的确实改变了,但打印时,e又被arry中的元素依次赋值,所以数组并没有被改变。
那么该如何实现呢? 我们可以用引用实现,e是arry数组中每个元素的别名,e的改变就会影响arry数组中值的改变。
- int main()
- {
- int arry[] = { 1,2,3,4,8,9 };
-
- for (auto& e : arry)
- {
- e++;
- }
-
- for (auto e : arry)
- {
- cout << e << " ";
- }
-
- return 0;
- }
那么用指针可以吗? 答案是不行的,因为arry中数据是int,不能将int 转化为int *
范围for必须是数组名,传参的时候相当于传了数组的首地址,也就是传了指针,不符合范围for的语法规定
NULL实际是一个宏,在传统的C头文件(stdio.h)中,可以看到如下代码:
- #ifndef NULL
- #ifdef __cplusplus
- #define NULL 0
- #else
- #define NULL ((void *)0)
- #endif
- #endif
- void f(int a)
- {
- cout << "f(int)" << endl;
- }
-
-
- void f(int* a)
- {
- cout << "f(int*)" << endl;
- }
-
-
- int main()
- {
- f(0);
- f(NULL);
- return 0;
- }
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖,所以引入nullptr来解决这一问题。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。