目录:
1. C的提高 1-131P 时间七天
2. C++的基础 132-286P 时间八天
3. C++的提高 287-378P 时间五天
4. C/C++的数据结构 379-482P 时间五天
5. C/C++的设计模式基础 483-540P 时间三天
视频资料:https://www.bilibili.com/video/av27904891?from=search&seid=10891514449061956870
P151 引用的本质剖析
引用的意义
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2)引用相对于指针来说具有更好的可读性和实用性
引用本质思考
#includeusing namespace std; //1.单独定义的引用时,必须初始化,说明很像一个常量 void main01() { const int c1=10; int a=10; int &b=a;//b很像一个常量 printf("&a:%d\n",&a); printf("&b:%d\n",&b);//a和b均是同一块内存空间的门牌号 system("pause"); return; }
//2.普通引用有自己的空间 struct Teacher { char name[64];//64 int age;//4 int &a;//4 0 很像指针所占的内存大小 int &b;//4 0 }; void main() { printf("sizeof(Teacher):%d\n",sizeof(Teacher));//76 system("pause"); return; }
//3. 引用的本质 void modifyA(int &a1) { a1=100; } void modifyA2(int *const a1) { *a1=200;//*实参的地址,去间接的修改实参的值 } void main03() { int a=10; //1 modifyA(a);//执行这个函数调用的时候,不需要取a的地址 printf("a:%d\n",a); //2 modifyA2(&a);//如果是指针需要手工取实参的地址 printf("a:%d\n",a); system("pause"); return; }
//4. 间接赋值 void modifyA3(int *p) { *p=300;//3. *p } void main04() { int a=10; int *p=NULL;//间接赋值的三个条件 定义两个变量 p=&a; *p=100; { *p=200; } modifyA3(&a);//2. 建立关联 }
引用的本质
1)引用在C++中的内部实现是一个常指针
Type& name <-->Type* const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
4) 对比间接赋值成立的三个条件
1、定义两个变量 (一个实参一个形参)
2、建立关联 实参取地址传给形参
3、*p形参去间接的修改实参的值
引用结论
1)引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一
//当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)
2)当我们使用引用语法的时,我们不去关心编译器引用是怎么做的
当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的
P152 函数返回值是引用(引用当左值)
C++引用使用时的难点:
当函数返回值为引用时
若返回栈变量
不能成为其它引用的初始值
不能作为左值使用
若返回静态变量或全局变量
可以成为其他引用的初始值
即可作为右值使用,也可作为左值使用
C++链式编程中,经常用到引用,运算符重载专题
#includeusing namespace std; int getA1() { int a; a=10; return a; } //返回a的本身 int& getA2() { int a;//如果是返回栈上的引用有可能会有问题 a=10; return a; } int* getA3() { int a; a=10; return &a; } void main() { int a1=getA1(); int a2=getA2(); int &a3=getA2(); printf("a1:%d\na2:%d\na3:%d\n",a1,a2,a3); system("pause"); return; }
函数当左值
//返回变量的值 int g1() { static int a=10; a++; return a; } //返回变量本身 int& g2() { static int a=10; a++; return a; } void main() { g1()=100; g2()=100; system("pause"); }
出现错误
//----函数当左值 //返回变量的值 int g1() { static int a=10; a++; return a; } //返回变量本身 int& g2() { static int a=10; a++; printf("a:%d\n",a); return a; } void main() { //g1()=100; g2()=100;//函数返回值是一个引用,并且当左值 g2(); int c=g2();//函数返回值是一个引用,并且当右值 system("pause"); }
输出结果
P153 小结
1、命名空间
2、输入和输出流
3、面向过程和面向对象
4、类的抽象,类的实例化
成员函数
5、类的扩展:类型加强,函数加强,异常加强
6、namespace命名空间:
当使用
使用using namespace std;
7、实用性增强:定义变量更加自由
8、register增强
9、struct类型增强
10、C++中所有的变量和函数都必须有类型
11、新增Bool类型关键字
12、三目运算符增强
在C语言中,表达式返回的是变量的值
在C++中,表达式返回的是变量的本身
13、const
C++符号表机制,当碰到常量声明时,会将常量放入符号表中
C语言中的const变量是只读变量,有自己的存储空间
C++中的const常量可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其他文件中使用
当使用&操作符取const常量的地址
14、引用
对一段连续的内存空间取一个别名
引用是一个常量指针
函数当左值必须返回一个引用
P154 指针的引用
需要重新理解。。。。
注:VS注释快捷键:Ctrl+K,Ctrl+C
取消注释快捷键:Ctrl+K,Ctrl+U
P155 常引用
使用变量初始化const引用
#includeusing namespace std; void main() { //普通引用 int a=10; int &b=a; printf("b:%d\n",b); //常引用 int x=20; const int &y=x;//常引用 让变量引用只读属性,不能通过y去修改x了 //y=21; //常引用 初识化 分为2种情况 //1. 用变量初始化 常引用 { int x1=30; const int &y1=x1;//用x1变量去初始化 常引用 } //2. 用字面量去初始化 常引用 { const int a=10;//c++编译器把a放在符号表中 //int &m=41;//普通引用 引用一个字面量 //引用就是给内存取多个别名 const int &m=42;//c++编译器会分配内存空间 } system("pause"); return; }
P156 inline内联函数
C++中推荐使用内联函数替代宏代码片段
C++中使用inline关键字声明内联函数
说明1
必须inline int myfunc(int a, int b)和函数体的实现,写在一块
说明2
内联函数在最终生成的代码中是没有定义的
C++编译器直接将函数体插入在函数调用的地方
内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
说明3
C++编译器不一定准许函数的内联请求!
说明4
内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
内联函数是对编译器的一种请求,因此编译器可能拒绝这种请求
内联函数由 编译器处理,直接将编译后的函数体插入调用的地方
宏代码片段 由预处理器处理, 进行简单的文本替换,没有任何编译过程
说明5
现代C++编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译
另外,一些现代C++编译器提供了扩展语法,能够对函数进行强制内联
如:g++中的__attribute__((always_inline))属性
说明6
C++中内联编译的限制:
不能存在任何形式的循环语句
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
函数内联声明必须在调用语句之前
结论:
1)内联函数在编译时直接将函数体插入函数调用的地方
2)inline只是一种请求,编译器不一定允许这种请求
3)内联函数省去了普通函数调用时压栈,跳转和返回的开销
//带参数的宏 #define MYFUNC(a, b) ((a) < (b) ? (a) : (b)) inline int myfunc(int a, int b) { return a < b ? a : b; } int main() { int a = 1; int b = 3; //int c = myfunc(++a, b); //a=2,b=3,c=2 int c = MYFUNC(++a, b); //(++a) < (b) ? (++a) : (b)-->a=3,b=3,c=3 printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %d\n", c); system("pause"); return 0; }
P157 函数参数相关扩展
默认参数
C++中可以在函数声明时为参数提供一个默认值,
当函数调用时没有指定这个参数的值,编译器会自动用默认值代替
#includeusing namespace std; void myPrint(int x=3) { cout<<"x:"< endl; } //2. 一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数值 void myPrint2(int m,int x=3,int y=4) { cout<<"x:"< endl; } void main() { //1.若填写参数,使用填写的,不填写默认 myPrint(4); myPrint(); system("pause"); return; }
函数占位参数
//函数占位参数,函数调用时必须写够参数 void func1(int a,int b,int) { cout<<"a:"<" b:"<endl; } void main() { //func1(1,2); func1(1,2,3); system("pause"); return; }
默认参数和占位参数
void func2(int a, int b,int =0) { cout<<"a:"<" b:"<endl; } void main() { func2(1,2); func2(1,2,3); system("pause"); return; }
输出结果:
P158 函数的重载
1、函数重载概念
函数重载(Function Overload)
用同一个函数名定义不同的函数
当函数名和不同的参数搭配时函数的含义不同
2 、函数重载的判断标准
函数重载至少满足下面的一个条件:
参数个数不同
参数类型不同
参数顺序不同
3、函数返回值不是函数重载的判断标准
实验1:调用情况分析;实验2:判断标准
函数重载的调用准则
编译器调用重载函数的准则
将所有同名函数作为候选者
尝试寻找可行的候选函数
精确匹配实参
通过默认参数能够匹配实参
通过默认类型转换匹配实参
匹配失败
最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。
无法匹配所有候选者,函数未定义,编译失败。
函数重载的注意事项
重载函数在本质上是相互独立的不同函数(静态链编)
重载函数的函数类型是不同的
函数返回值不能作为函数重载的依据
函数重载是由函数名和参数列表决定的。
#includeusing namespace std; void myPrint(int a) { printf("a:%d\n",a); } void myPrint(char *p) { printf("p:%s\n",p); } void myPrint(int a,int b) { printf("a:%d\n",a); printf("b:%d\n",b); } /* // 返回值不是函数重载的判断 int myPrint(int a,int b) { printf("a:%d\n",a); printf("b:%d\n",b); } */ //1. 当函数名和不同的参数搭配时函数的含义不同 //2. 函数重载的判断标准 // 名称 参数 返回值 // 名称相同,参数不一样(个数/类型) //3. 返回值不是函数重载的判断 //4. 重载函数调用标准 // void main() { myPrint(1); myPrint("aaafssfs"); myPrint(1,2); system("pause"); return; }
函数默认参数 和 函数重载在一起
void myfunc(int a,int b,int c=0) { printf("a:%d b:%d c:%d\n",a,b,c); } void myfunc(int a,int b) { printf("a:%d b:%d \n",a,b); } void main() { //myfunc(1,2);//函数调用时会产生二义性 system("pause"); return; }
P159 函数重载和指针在一起
函数重载与函数指针
当使用重载函数名对函数指针进行赋值时
根据重载规则挑选与函数指针参数列表一致的候选者
严格匹配候选者的函数类型与函数指针的函数类型
#includeusing namespace std; void myfunc(int a) { printf("a:%d\n",a); } void myfunc(char *p) { printf("p:%s\n",p); } void myfunc(int a,int b) { printf("a:%d\n",a); printf("b:%d\n",b); } void myfunc(char *p1,char *p2) { printf("p1:%d\n",p1); printf("p2:%d\n",p2); } //函数指针 //声明一个函数类型 //void myfunc(int a,int b) typedef void (myTypeFunc)(int a,int b);//int //声明一个函数指针类型 typedef void (*myPTypeFunc)(int a,int b);//声明一个指针的数据类型 //myPTypeFunc fp=NULL;通过函数指针类型 定义了一个函数指针 //定义一个函数指针变量 void (*myVarPFunc)(int a,int b); void main() { //myTypeFunc *myfuncp=NULL;//定义一个函数指针,这个指针指向函数的入口地址 myPTypeFunc fp;//定义了一个函数指针变量 fp=myfunc; //fp(1); fp(1,2); /*{ char buf1[]="aaa"; char buf2[]="bbb"; fp(buf1,buf2); }*/ system("pause"); return; }