学习一门新的语言,我们总体上要学习一下它的关键字
1.C++关键字
慢工出细活,不要求上来就全部掌握,在学习前看一下总体做到心中有数
C++兼容C的特性
2.namespace关键字
为什么要提出这个关键字?
因为C语言无法解决命名冲突问题
①我们自己定义的变量和函数可能比库里面的变量重名冲突
②当做到代码量比较大的项目的时候,队里两个人写的代码会产生命名冲突
namespace域
全局和局部同时的情况下,首先在局部找,再去全局找
#include<iostream>
#include<stdlib.h>
namespace zkx {
//再来这边找
}
int main() {
//先来这边找
}
因此使用“::”域作用限定符来限定
符号的左边是域名,右边是域中的变量/函数
命名空间可以定义函数,结构体类型,还可以嵌套
不在一个文件内的命名空间会被合并
#include<iostream>
#include<stdlib.h>
namespace zkx {
//再来这边找
int rand = 0;
int Add(int a, int b) {
return a + b;
}
struct Node {
}node;
namespace zkx2 {
}
}
int main() {
//先来这边找
//printf("%d", rand);这么写会报错,因为先去局部main里找,再去全局
printf("%d", zkx::rand);
zkx::Add(1, 2);
zkx::Node;
return 0;
}
命名空间的使用有三种方式:
①加上作用域限定符::
好处:指定作用域,做到命名隔离
坏处:使用起来不方便
②使用using将命名空间的某个成员引入
③使用using namespace 命名空间引入
好处:用起来很方便
坏处:隔离失效,谨慎使用!!
项目中推荐使用①或②的方式
日常练习,不在乎命名污染,using namespace std;全部展开
项目中,不要全部展开,指定访问或展开部分
3.C++输入输出
#include<iostream>
using namespace std;
int main() {
cout << "hello world" << endl;//<<流插入符号
printf("hello world");//等价于上面
//cout的优势:流不用考虑类型,可以自动识别
int a = 10;
double b = 1.11;
cout << b << " " << a << endl;
//cin同样可以读取不同类型
//>>流提取运算符
cin >> a >> b;
cout << b << " " << a << endl;
//cout和printf ,哪个方便就使用哪个
return 0;
}
4.缺省函数
只有一个缺省参数
全缺省:所有参数有给了缺省值
半缺省 --不是缺一半,而是缺一部分 --并且是从右往做缺省,连续缺省
一个缺省应用
注意点:省略不能在声明和定义中同时出现
C语言不支持缺省
5.函数重载
概念:C++中允许在同一作用域声明的同名函数
参数个数 or 类型 or 顺序 不同, 就可实现函数重载
①类型不同
②个数不同
③顺序不同
注意:
①返回值不同,不能构成重载!
②缺省值不同,也不能构成重载
构成重载,但存在二义性的情况
在无参的情况下不知道运用谁
6.引用
引用不是定义一个新的变量,而是给已经存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它的引用共用一块内存空间。
比如说:小明,在家也被叫做“狗蛋”,在学校叫“一鸣”
引用的价值:
1.做参数 --提高效率;形参的修改可以修改实参(输出型参数)
2.引用做返回值(修改返回变量) --传值返回,会拷贝给临时变量,临时变量做返回;传引用返回,返回的是返回对象的引用(别名)- -函数返回时,出来函数作用域,返回对象还在,则可以用引用返回,如果已经还给系统了,必须用传值返回
语法层:
指针和引用是完全不同的概念
指针是开空间,存储变量地址
引用不开空间,仅仅是对变量取别名
底层汇编实现时:
引用是用指针实现的
了解一下即可,用的时候不要想底层汇编如何实现
引用和取地址
#include<iostream>
using namespace std;
int main() {
int a = 10;
//引用
int& b = a;
//取地址
int* p = &a;
return 0;
}
引用在定义的时候必须要初始化
一个变量可以有多个引用
一旦引用了一个实体,就不能引用其他实体了
//引用在定义时一定要初始化
int a = 10;
int& b = a;
//一个变量可以有多个引用
int c;
int& c1 = c;
int& c2 = c;
int& c3 = c;
引用传参数
//引用做参数
//三种传的类型,传值传址传引用
void swap(int& r1, int& r2) {//传引用
int temp = r1;
r1 = r2;
r2 = temp;
}
void swap(int* r1, int* r2) {//传址
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
void swap(int r1, int r2) {//传值
int temp = r1;
r1 = r2;
r2 = temp;
}
//他们构成函数重载
//但是swap(x,y);调用时存在歧义,他不知道调用,传值还是传引用
//类似
//void f()
//void f(int x = 0, int y = 0)
int main() {
int x = 0, y = 1;
//swap(x, y);
swap(&x, &y);
return 0;
}
引用做返回值
先来看看传值返回
return 的时候会产生一个零时变量,而不是直接传过去的
那么零时变量存在哪呢?
1.如果c比较小(4 or 8),一半是寄存器充当零时变量
2.如果c比较大,临时变量存放在ADD的栈帧中
什么情况会产生临时变量:
类型转换,类型的截断,提升,函数返回值,拷贝
引用返回:
不生成c的拷贝返回,而是直接返回c的引用
c的别名也就是c
当前代码的问题:
1.存在非法访问,因为Add(1,2)的返回值是c的引用,所以Add栈帧销毁了以后,会去访问c位置空间
2.如果Add函数栈帧销毁,清理空间,那么c取值就是随机值,给的ret也就似乎随机值,取决于编译器实现
函数栈帧被覆盖
举例:租房子
使用传引用返回的情况
修改返回变量
int& At(int i) {
static int a[10];
return a[i];
}
int main() {
for (size_t i = 0; i < 10; i++)
At(i) = 10 + i;
for (size_t i = 0; i < 10; i++)
cout << At(i) << endl;
return 0;
}
为什么不能传值?
常引用
int是可读可写
const int& 是可读
传参时不想改变某个值经量传引用
int main() {
//权限的放大 不可以
/*const int a = 10;
int& b = a;*/
//权限不变,可以
const int a = 10;
const int& b = a;
//权限的缩小 可以
int c = 5;
const int& d = c;
return 0;
}
细说c语言中的整型提升
其实是产生了一个临时变量,通过临时变量赋值
然而临时变量是右值,具有常性
所以必须要加const
右值:
常见的有
①常量
②表达式产生的临时变量比如x1+x2
结论:const Type&可以接受任意类型的对象
指针和引用的区别(面试常考):
理解即可,不必背诵
- 引用概念上定义了一个变量的别名(没开空间),指针存储了一个变量的地址
- 引用在定义时必须要初始化,指针最好初始化(指向空),不初始化也不会报错
- 没有NULL引用,但有NULL指针
- 在sizeof的含义不同:引用结果是引用对象的大小,但指针始终是地址空间所占字节数(32位平台上4个字节,64位8个)
- 引用自加是引用的实体自加1,指针自加是向后偏移一个类型的大小
- 访问实体方式不同,指针需要解引用,引用编译器自己处理
- 有多级指针,没有多级引用
- 引用比指针更加安全
7.extern “C”
c++调用c的动静态库(不了解可暂时理解为被编译好的程序)
调用前需在属性页常规里配置静态库的目录和输入中配置附加依赖项.lib
可以用来保护源代码,公司协作
c项目调用c++的静态库
总结:
c++程序调用c的库,在c++程序中加extern “C”
C程序调用c++的库,在c++的库中调用extern “C”
*
8.内联函数
对于频繁调用的小函数,有没有什么优化的方式
先用宏实现函数的调用
宏的优势:直接替换成表达式,不用建立栈帧
//#define ADD(x,y)(x + y)
#define ADD(x,y)((x)+(y))
int main() {
int x = 1, y = 2;
cout << ADD(1, 2) << endl;
cout << ADD(x | y, x & y) << endl;//(x|y+x&y)我们发现+优先级比|和&高,所以修改
return 0;
}
c++使用内联函数
vs中realease版本可以展开
有了内联,我们就不需要取用C的宏,宏很复杂,很容易出错
inline int Add(int x, int y) {
return x + y;
}
int main() {
int x = 1, y = 2;
cout << Add(1, 2) << endl;
return 0;
}
内联是一种空间换时间的做法,省去调用函数栈帧的开销,代码很长(10行以上,不同编译器不同)或者有递归的函数不适合使用内联
结论:短小,频繁调用的函数建议使用内联
9.auto关键字(C++11)
自动推断类型,但不会显示const属性
int main() {
int a = 10;
//auto自动推断类型
auto b = a;
auto c = 'a';
auto d = 1.11;
//typeid().name查询类型
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
实际中,我们用auto来推荐复杂的类型(比如map)
auto引用必须要加&符号
auto不用用来推数组
auto不用来作为函数的形参
语法糖————范围for
int main() {
int arr[] = { 1,2,3,4,5,6 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
cout << arr[i] << endl;
}
//范围for
for (auto e : arr) {
cout << e << endl;
}
return 0;
}
int main() {
int arr[] = { 1,2,3,4,5,6 };
//for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
//{
// cout << arr[i] << endl;
//}
范围for
//for (auto e : arr) {
// cout << e << endl;
//}
//范围for 将数组中的每个元素都加1
for (auto e : arr) {
e++;
}//这种方法是无效的,e是一个局部变量
//auto&是将array中的数给e,只不过加&e就是array的别名,所以可以修改
for (auto& e : arr) {
cout << e << endl;
}
for(auto x:arr)
cout << x << endl;
return 0;
}
10.指针空值nullptr
c++不推荐使用NULL
而是使用nullptr
可以达到正确的匹配int*,而NULL和0在特殊情况都匹配的是int而不是int*
总结:初始化空指针推荐使用nullptr