- 简单的C++程序
1.1求圆的周长和面积
数据描述:
半径,周长,面积均用实型数表示
数据处理:
输入半径 r;
计算周长 = 2πr ;
计算面积 = π* r2 ;
输出半径,周长,面积;
方法1:用结构化方法编程,求圆的周长和面积
// count the girth and area of circle
#include<iostream>
using name std;
void main ()
{ double r, girth, area ;
const double PI = 3.1415 ;
cout << "Please input radius:\n" ; //操作符重载
cin >> r ; //输入
girth = 2 * PI * r ;
area = PI * r * r ;
cout << "radius = " << r << endl ;
cout << "girth = " << girth << endl ;
cout << "area = " << area << endl ;
}
方法2:用面向对象方法编程,求圆的周长和面积
#include<iostream.h>
using name std;
class Circle
{ double radius ; //成员变量
public : //类的访问控制
void Set_Radius( double r ) { radius = r ; } //成员函数
double Get_Radius() { return radius ; } //通过成员函数设置成员变量
double Get_Girth() { return 2 * 3.14f * radius ; } //通过成员函数获取成员变量
double Get_Area() { return 3.14f * radius * radius ; }
} ;
void main()
{
Circle A, B ; //用类定义对象
A.Set_Radius( 6.23 ) ; //类的调用
cout << "A.Radius = " << A.Get_Radius() << endl ;
cout << "A.Girth = " << A.Get_Girth() << endl ;
cout << "A.Area = " << A.Get_Area() << endl ;
B.Set_Radius( 10.5 ) ;
cout << "B.radius = " << B.Get_Radius() << endl ;
cout << "B.Girth=" << B.Get_Girth() << endl ;
cout << "B.Area = " << B.Get_Area() << endl ;
}
总结:建立类、对象、成员变量、成员函数,输入输入流基本概念。
1.2初学者易犯错误模型
// demo02_circle_err.cpp
#include<iostream>
using namespace std;//c++的命名空间
class circle
{
public:
double r;
double pi = 3.1415926;
double area = pi*r*r;
};
int main()
{
circle pi;
cout << "请输入area" << endl;
cin >> pi.r;
cout << pi.area << endl; //乱码
system("pause");
return 0;
}
这是因为初始化的时候成员变量r未知。
总结:从内存四区的角度,解释为什么会出现乱码理解为什么需要成员函数
2程序设计方法的发展历程
面向过程的结构化程序设计方法
设计思路
– 自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。
程序结构:
– 按功能划分为若干个基本模块,形成一个树状结构。
– 各模块间的关系尽可能简单,功能上相对独立;每一模块内部均是由顺序、选择和循环三种基本结构组成。
– 其模块化实现的具体方法是使用子程序。
优点:
有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子任务,便于开发和维护。
缺点:可重用性差、数据安全性差、难以开发大型软件和图形界面的应用软件
– 把数据和处理数据的过程分离为相互独立的实体。
– 当数据结构改变时,所有相关的处理过程都要进行相应的修改。
– 每一种相对于老问题的新方法都要带来额外的开销。
– 图形用户界面的应用程序,很难用过程来描述和实现,开发和维护也都很困难。
面向对象的方法
将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。
对同类型对象抽象出其共性,形成类。
类通过一个简单的外部接口,与外界发生关系。
对象与对象之间通过消息进行通信。
面向对象的基本概念
对象
一般意义上的对象:
– 是现实世界中一个实际存在的事物。
– 可以是有形的(比如一辆汽车),也可以是无形的(比如一项计划)。
– 是构成世界的一个独立单位,具有
静态特征:可以用某种数据来描述
动态特征:对象所表现的行为或具有的功能
面向对象方法中的对象:
– 是系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。对象由一组属性和一组行为构成。
– 属性:用来描述对象静态特征的数据项。
– 行为:用来描述对象动态特征的操作序列。
类
分类——人类通常的思维方法
分类所依据的原则——抽象
– 忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的概念。
– 例如,石头、树木、汽车、房屋等都是人们在长期的生产和生活实践中抽象出的概念。
面向对象方法中的"类"
– 具有相同属性和服务的一组对象的集合
– 为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。
– 类与对象的关系:
犹如模具与铸件之间的关系,一个属于某类的对象称为该类的一个实例。
封装
也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
把对象的属性和服务结合成一个独立的系统单元。
尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的对外接口使之与外部发生联系。
继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原因之一。
定义:特殊类的对象拥有其一般类的全部属性与服务,称作特殊类对一般类的继承。
例如:将轮船作为一个一般类,客轮便是一个特殊类。
多态
多态是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。
面向对象的软件工程
面向对象的软件工程是面向对象方法在软件工程领域的全面应用。它包括:
– 面向对象的分析(OOA)
– 面向对象的设计(OOD)
– 面向对象的编程(OOP)
– 面向对象的测试(OOT)
– 面向对象的软件维护(OOSM)
总结:
面向过程程序设计:数据结构 + 算法
主要解决科学计算问题,用户需求简单而固定
特点:
分析解决问题所需要的步骤
利用函数实现各个步骤
依次调用函数解决问题
问题:
软件可重用性差
软件可维护性差
构建的软件无法满足用户需求
面向对象程序设计:由现实世界建立软件模型
将现实世界中的事物直接映射到程序中,可直接满足用户需求
特点:
直接分析用户需求中涉及的各个实体
在代码中描述现实世界中的实体
在代码中关联各个实体协同工作解决问题
优势:
构建的软件能够适应用户需求的不断变化
直接利用面向过程方法的优势而避开其劣势
3 C语言和C++语言关系
C语言是在实践的过程中逐步完善起来的
没有深思熟虑的设计过程
使用时存在很多“灰色地带”
残留量过多低级语言的特征
直接利用指针进行内存操作
C语言的目标是高效
最终程序执行效率的高效
当面向过程方法论暴露越来越多的缺陷的时候,业界开始考虑在工程项目中引入面向对象的设计方法,而第一个需要解决的问题就是:高效的面向对象语言,并且能够兼容已经存在的代码。
C语言 + 面向对象方法论===》Objective C /C++
C语言和C++并不是对立的竞争关系
C++是C语言的加强,是一种更好的C语言
C++是以C语言为基础的,并且完全兼容C语言的特性
学习C++并不会影响原有的C语言知识,相反会根据加深对C的认知;
学习C++可以接触到更多的软件设计方法,并带来更多的机会。
1) C++是一种更强大的C,通过学习C++能够掌握更多的软件设计方法
2) C++是Java/C#/D等现代开发语言的基础,学习C++后能够快速掌握这些语言
3)C++是各大知名软件企业挑选人才的标准之一
4 C++对C的加强
4.1 namespace命名空间
1 C++命名空间基本常识
所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
一 :<iostream>和<iostream.h>格式不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。后缀为.h的头文件c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 因此,
1)当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;
2)当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。
二: 由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
1、直接指定标识符。例如std::ostream而不是ostream。完整语句如下: std::cout << std::hex << 3.4 << std::endl;
2、使用using关键字。 using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl;
3、最方便的就是使用using namespace std; 例如: using namespace std;这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写: cout <<hex << 3.4 << endl;因为标准库非常的庞大,所以程序员在选择的类的名称或函数名 时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问 题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。所以就有了<iostream.h> 和<iostream>等等这样的头文件,一个是为了兼容以前的C++代码,一个是为了支持新的标准。命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加".h"
2 C++命名空间定义及使用语法
/*
在C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,
标准C++引入了关键字namespace(命名空间/名字空间/名称空间/名域),可以更好地控制标识符的作用域。
/
/
std是c++标准命名空间,c++标准程序库中的所有标识符都被定义在std中,比如标准库中的类iostream、vector
等都定义在该命名空间中,使用时要加上using声明(using namespace std) 或using指示(如std::string、
std::vector<int>).
/
/
C中的命名空间
在C语言中只有一个全局作用域
C语言中所有的全局标识符共享同一个作用域
标识符之间可能发生冲突
C++中提出了命名空间的概念
命名空间将全局作用域分成不同的部分
不同命名空间中的标识符可以同名而不会发生冲突
命名空间可以相互嵌套
全局作用域也叫默认命名空间
/
/
C++命名空间的定义:
namespace name { … }
/
/
C++命名空间的使用:
使用整个命名空间:using namespace name;
使用命名空间中的变量:using name::variable;
使用默认命名空间中的变量:::variable
默认情况下可以直接使用默 认命名空间中的所有标识符
*/
3 C++命名空间编程实践
#include<iostream>
namespace NameSpaceA
{
int a = 0;
}
namespace NameSpaceB
{
int a = 1;
namespace NameSpaceC
{
struct Teacher
{
char name[10];
int age;
};
}
}
int main()
{
using namespace NameSpaceA;
using NameSpaceB::NameSpaceC::Teacher;
printf("a = %d\n", a);
printf("a = %d\n", NameSpaceB::a);
NameSpaceB::NameSpaceC::Teacher t2;
Teacher t1 = { "aaa", 3 };
printf("t1.name = %s\n", t1.name);
printf("t1.age = %d\n", t1.age);
system("pause");
return 0;
}
结果为:
结论
1) 当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std ,需要这样做。std::cout。
2) c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。
3) C++命名空间的定义: namespace name { … }
4) using namespace NameSpaceA;
5) namespce定义可嵌套。
4.2 “实用性”增加
#include "iostream"
using namespace std;
//C语言中的变量都必须在作用域开始的位置定义!!
//C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。
int main11()
{
int i = 0;
printf("ddd");
int k;
system("pause");
return 0;
}
这个功能非常好,不用每次把要用的变量都放到最前面先定义好。
4.3 register关键字增强
//register关键字 请求编译器让变量a直接放在寄存器里面,速度快
//在c语言中 register修饰的变量 不能取地址,但是在c++里面做了内容
/*
//1
register关键字的变化
register关键字请求“编译器”将局部变量存储于寄存器中
C语言中无法取得register变量地址
在C++中依然支持register关键字
C++编译器有自己的优化方式,不使用register也可能做优化
比如:
//register关键字 请求编译器让变量a直接放在寄存器里面,速度快
//在c语言中 register修饰的变量 不能取地址,但是在c++里面做了内容
/*
//1
register关键字的变化
register关键字请求“编译器”将局部变量存储于寄存器中
C语言中无法取得register变量地址
在C++中依然支持register关键字
C++编译器有自己的优化方式,不使用register也可能做优化
C++中可以取得register变量的地址
//2
C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。
//3
早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。
*/
int main22()
{
register int a = 0;
printf("&a = %x\n", &a);
system("pause");
return 0;
}
C++中可以取得register变量的地址
//2
C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。
//3
早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。
*/
int main22()
{
register int a = 0;
printf("&a = %x\n", &a);
system("pause");
return 0;
}
4.4变量检测增强
#include<iostream>
int g_var;
int g_var = 1;
/*
在C语言中,重复定义多个同名的全局变量是合法的
在C++中,不允许定义多个同名的全局变量
C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
int g_var;
int g_var = 1;
C++直接拒绝这种二义性的做法。
*/
int main(int argc, char *argv[])
{
printf("g_var = %d\n", g_var);
return 0;
}
结果为:
4.5 struct类型加强
struct类型的加强:
C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型
C++中的struct是一个新类型的定义声明
struct Student
{
char name[100];
int age;
};
int main(int argc, char *argv[])
{
Student s1 = {"wang", 1};
Student s2 = {"wang2", 2};
return 0;
}
上面的代码如果在c语言中会报错,因为不认为Student是一个类型。
4.6 C++中所有的变量和函数都必须有类型
#include<iostream>
/*
C++中所有的变量和函数都必须有类型
C语言中的默认类型在C++中是不合法的
函数f的返回值是什么类型,参数又是什么类型?
函数g可以接受多少个参数?
*/
//更换成.cpp试试
f(i)
{
printf("i = %d\n", i);
}
g()
{
return 5;
}
int main(int argc, char *argv[])
{
f(10);
printf("g() = %d\n", g(1, 2, 3, 4, 5));
getchar();
return 0;
}
上面的代码在c语言中可以运行但是在c++中无法运行
总结:
/*
在C语言中
int f( );表示返回值为int,接受任意参数的函数
int f(void);表示返回值为int的无参函数
在C++中
int f( );和int f(void)具有相同的意义,都表示返回值为int的无参函数
*/
C++更加强调类型,任意的程序元素都必须显示指明类型
4.2-4.6属于语法级别的增强。
4.7新增Bool类型关键字
/*
C++中的布尔类型
C++在C语言的基本类型系统之上增加了bool
C++中的bool可取的值只有true和false
理论上bool只占用一个字节,
如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现
true代表真值,编译器内部用1来表示
false代表非真值,编译器内部用0来表示
bool类型只有true(非0)和false(0)两个值
C++编译器会在赋值时将非0值转换为true,0值转换为false
*/
int main(int argc, char *argv[])
{
int a;
bool b = true;
printf("b = %d, sizeof(b) = %d\n", b, sizeof(b));
b = 4;
a = b;
printf("a = %d, b = %d\n", a, b);
b = -4;
a = b;
printf("a = %d, b = %d\n", a, b);
a = 10;
b = a;
printf("a = %d, b = %d\n", a, b);
b = 0;
printf("b = %d\n", b);
system("pause");
return 0;
}
运行结果为:
所以,bool变量的值要么是true(1),要么是false(0).
4.8三目运算符功能增强
1三目运算符在C和C++编译器的表现
int main()
{
int a = 10;
int b = 20;
//返回一个最小数 并且给最小数赋值成3
//三目运算符是一个表达式 ,表达式不可能做左值
(a < b ? a : b )= 30;
printf("a = %d, b = %d\n", a, b);
system("pause");
return 0;
}
结果为:
2结论
1)C语言返回变量的值 C++语言是返回变量本身
C语言中的三目运算符返回的是变量值,不能作为左值使用
C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方
2)注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
(a < b ? 1 : b )= 30;
3)C语言如何支持类似C++的特性呢?
====>当左值的条件:要有内存空间;C++编译器帮助程序员取了一个地址而已
思考:如何让C中的三目运算法当左值呢?
可以返回地址。
*(a < b ? &a : &b) = 40;
5 C/C++中的const
1 const基础知识(用法、含义、好处)
int main()
{
const int a;
int const b;
const int *c;
int * const d;
const int * const e ;
return 0;
}
Int func1(const )
初级理解:const是定义常量==》const意味着只读
含义:
//第一个第二个意思一样 代表一个常整形数
//第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
//第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
//第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
Const好处
//合理的利用const,
//1指针做函数参数,可以有效的提高代码可读性,减少bug;
//2清楚的分清参数的输入和输出特性
int setTeacher_err( const Teacher *p)
Const修改形参的时候,在利用形参不能修改指针所向的内存空间
比如下面的例子:
//0 const的基础知识
struct Teacher
{
char name[64];
int age;
};
//指针所指向的内存空间,不能被修改
int operatorTeacher01(const Teacher *pT)
{
//pT->age = 10;
return 0;
}
//指针变量本身不能被修改
int operatorTeacher02(Teacher * const pT)
{
pT->age = 10;
//pT = NULL; //
return 0;
}
int operatorTeacher03(const Teacher * const pT)
{
//pT->age = 10;
//pT = NULL; //
printf("age:%d", pT->age);
return 0;
}
2 C中“冒牌货”
//c语言中的const是一个冒牌货
//C++语言中 const是一个真正的常量
int main()
{
const int a = 10;
int *p = (int*)&a;
printf("a===>%d\n", a);
*p = 11;
printf("a===>%d\n", a);
printf("Hello......\n");
return 0;
}
上面的代码在c++的结果为:
但是在C语言中,却可以修改。
解释:(符号表的使用)
C++编译器对const常量的处理
当碰见常量声明时,在符号表中放入常量 =》问题:那有如何解释取地址
编译过程中若发现使用常量则直接以符号表中的值替换
编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)
?联想: int &a = 1(err) & const int &a = 10(ok)?
注意:
C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。
结论:
C语言中的const变量
C语言中const变量是只读变量,有自己的存储空间
C++中的const常量
可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其它文件中使用时(如extern),会分配
当使用&操作符取const常量的地址时,会分配
注意:合适分配const变量的内存?
实在编译的时候分配好内存的
//3 const分配内存的时机 编译器编译器期间
void main83()
{
//好像 a 是一个常量
int a;
const int b = 10;
int c;
printf("&a:%d, &b:%d, &c:%d \n", &a, &b, &c);
system("pause");
}
结果为:
b的地址在a和c的地址中间,说明实在编译的时候就分配好了,而不是后来才分配的的
3 const和#define相同之处
//练习 解释为什么
//#define N 10
int main()
{
const int a = 1;
const int b = 2;
int array[a + b ] = {0};
int i = 0;
for(i=0; i<(a+b); i++)
{
printf("array[%d] = %d\n", i, array[i]);
}
getchar();
return 0;
}
C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了。
4 const和#define的区别
对比加深
C++中的const常量类似于宏定义
const int c = 5; ≈ #define c 5
C++中的const常量与宏定义不同
const常量是由编译器处理的,提供类型检查和作用域检查
宏定义由预处理器处理,单纯的文本替换
//在func1定义a,在func2中能使用吗?
//在func1中定义的b,在func2中能使用吗?
练习
void fun1()
{
#define a 10
const int b = 20;
//#undef a # undef
}
void fun2()
{
printf("a = %d\n", a);
//printf("b = %d\n", b);
}
int main()
{
fun1();
fun2();
return 0;
}
如果没有undef的时候,fun2可以访问宏定义的a,但是访问不了const修饰的b。这说明const修饰的变量是有作用域的。
5 结论
C语言中的const变量
C语言中const变量是只读变量,有自己的存储空间
C++中的const常量
可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其它文件中使用,会分配存储空间
当使用&操作符,取const常量的地址时,会分配存储空间
当const int &a = 10; const修饰引用时,也会分配存储空间
- 引用专题讲座
1 引用(普通引用)
变量名回顾
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间
问题1:对一段连续的内存空间只能取一个别名吗?
1 引用概念
a) 在C++中新增加了引用的概念
b) 引用可以看作一个已定义变量的别名
c) 引用的语法:Type& name = var;
d) 引用做函数参数那?(引用作为函数参数声明时不进行初始化)
void main01()
{
int a = 10; //c编译器分配4个字节内存。。。a内存空间的别名
int &b = a; //b就是a的别名。。。
a =11; //直接赋值
{
int *p = &a;
*p = 12;
printf("a %d \n",a);
}
b = 14;
printf("a:%d b:%d", a, b);
system("pause");
}
结果为:
引用是C++的概念
属于C++编译器对C的扩展
问题:C中可以编译通过吗?
int main()
{
int a = 0;
int &b = a; //int * const b = &a
b = 11; //*b = 11;
return 0;
}
结论:编译出错,请不要用C的语法考虑 b=11
3 引用做函数参数
普通引用在声明时必须用其它的变量进行初始化,
引用作为函数参数声明时不进行初始化
//05复杂数据类型 的引用
struct Teacher
{
char name[64];
int age ;
};
void printfT(Teacher *pT)
{
cout<<pT->age<<endl;
}
//pT是t1的别名 ,相当于修改了t1
void printfT2(Teacher &pT)
{
//cout<<pT.age<<endl;
pT.age = 33;
}
//pT和t1的是两个不同的变量
void printfT3(Teacher pT)
{
cout<<pT.age<<endl;
pT.age = 45; //只会修改pT变量 ,不会修改t1变量
}
void main()
{
//int &c ; //引用 必须要初始化
Teacher t1;
t1.age = 35;
printfT(&t1);
printfT2(t1); //pT是t1的别名
printf("t1.age:%d \n", t1.age); //33
printfT3(t1) ;// pT是形参 ,t1 copy一份数据 给pT //---> pT = t1
printf("t1.age:%d \n", t1.age); //35
cout<<"hello..."<<endl;
system("pause");
return ;
}
结果为:
4 引用的意义
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2)引用相对于指针来说具有更好的可读性和实用性
5 引用本质思考
思考1:C++编译器背后做了什么工作?
int main()
{
int a = 10;
int &b = a;
//b是a的别名,请问c++编译器后面做了什么工作?
b = 11;
cout<<"b--->"<<a<<endl;
printf("a:%d\n", a);
printf("b:%d\n", b);
printf("&a:%d\n", &a);
printf("&b:%d\n", &b); //请思考:对同一内存空间可以取好几个名字吗?
system("pause");
return 0;
}
结果为:
单独定义的引用时,必须初始化;说明很像一个常量可以发现引用变量和被引用变量的地址相同。说明a和b是同一块内存空间的门牌号。
思考2://2 普通引用有自己的空间吗? 有
#include <iostream>
using namespace std;
struct Teacer {
double &a;
double &b;
};
int main()
{
printf("sizeof(Teacher) %d\n", sizeof(Teacer));
system("pause");
return 0;
}
结果为
综合上面的两点,引用的本质为:
Type*const p (一个常用的指针)
引用是一个地址,引用是常量。
6 引用的本质
1)引用在C++中的内部实现是一个常指针
Type& name <->Type* const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
实际上c++编译器会把左图的那句话翻译成右边的话。
7 引用结论
1)引用在实现上:
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)
2)当我们使用引用语法的时,我们不去关心编译器引用是怎么做的
当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的
8 函数返回值是引用(引用当左值)【这是个重点】
C++引用使用时的难点:
1) 当函数返回值为引用时
若返回栈变量,不能成为其它引用的初始值,也不能作为左值使用
2) 若返回静态变量或全局变量
可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用
3) C++链式编程中,经常用到引用,运算符重载专题
什么是链式编程?比如cout<<"abc"<<"bcd"<<"edf";就是链式编程,其中cout<<"abc"返回的实际上就是一个引用,流的引用。
难点1的举例
#include <iostream>
using namespace std;
int getAA1()
{
int a;
a = 10;
return a;
}
//返回a的本身 返回a的一个副本 10
int& getAA2()
{
int a; //如果返回栈上的 引用, 有可能会有问题
a = 10;
return a;
}
void main()
{
int a1 = 0;
int a2 = 0;
a1 = getAA1();
a2 = getAA2(); //10
int &a3 = getAA2(); //若返回栈变量 不能成为其它引用的初始值
printf("a1:%d \n", a1);
printf("a2:%d \n", a2);
printf("a3:%d \n", a3); // *a3
cout << "hello..." << endl;
system("pause");
return;
}
结果为:
难点2的距离
#include <iostream>
using namespace std;
int j1()
{
static int a = 10;
a++;
return a;
}
int& j2()
{
static int a = 10;
a++;
return a;
}
//若返回静态变量或全局变量
// 可以成为其他引用的初始值
// 即可作为右值使用,也可作为左值使用
void main()
{
int a1 = 10;
int a2 = 20;
a1 = j1();
a2 = j2();
int &a3 = j2();
printf("a1:%d \n", a1);
printf("a2:%d \n", a2);
printf("a3:%d \n", a3);
system("pause");
}
结果为:
函数当左值
#include <iostream>
using namespace std;
//--- 函数当左值
//返回变量的值
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;
//11 = 100;
g2() = 100; //函数返回值是一个引用,并且当左值
g2();
int c1 = g1(); //函数返回值是一个引用,并且当右值
int c2 = g2(); //函数返回值是一个引用,并且当右值
system("pause");
}
结果为:
9.指针的引用
#include <iostream>
using namespace std;
#include "iostream"
using namespace std;
//
struct Teacher
{
char name[64];
int age;
};
//在被调用函数 获取资源
int getTeacher(Teacher **p)
{
Teacher *tmp = NULL;
if (p == NULL)
{
return -1;
}
tmp = (Teacher *)malloc(sizeof(Teacher));
if (tmp == NULL)
{
return -2;
}
tmp->age = 33;
// p是实参的地址 *实参的地址 去间接的修改实参的值
*p = tmp;
}
//指针的引用 做函数参数
int getTeacher2(Teacher* &myp)
{
//给myp赋值 相当于给main函数中的pT1赋值
myp = (Teacher *)malloc(sizeof(Teacher));
if (myp == NULL)
{
return -1;
}
myp->age = 36;
}
void FreeTeacher(Teacher *pT1)
{
if (pT1 == NULL)
{
return;
}
free(pT1);
}
void main()
{
Teacher *pT1 = NULL;
//1 c语言中的二级指针
getTeacher(&pT1);
cout << "age:" << pT1->age << endl;
FreeTeacher(pT1);
//2 c++中的引用 (指针的引用)
//引用的本质 间接赋值后2个条件 让c++编译器帮我们程序员做了。
getTeacher2(pT1);
cout << "age:" << pT1->age << endl;
FreeTeacher(pT1);
cout << "hello..." << endl;
system("pause");
}
结果为:
2 常量引用
1 使用变量初始化const引用
#include <iostream>
using namespace std;
//常引用的知识架构
void main1301()
{
//普通引用
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 = 40; //c++编译器把a放在符号表中
//int &m = 41; //普通引用 引用一个字面量 请问字面量有没有内存地址(没有),这句话会编译报错
//引用 就是给内存取多个门牌号 (多个别名),现在连内存地址都没有,当然去不了别名
//printf("&40:%d \n", &40);
const int &m = 43; //c++编译器 会 分配内存空间 ,让m为该内存空间的别名
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
结果为:
常引用容易出错点:
案例1:
在C++中可以声明const引用
const Type& name = var;
const引用让变量拥有只读属性
#include <iostream>
using namespace std;
int main()
{
int a = 10;
const int &b = a;
//int *p = (int *)&b;
//b = 11; //err
a = 11;
//*p = 11; //只能用指针来改变了
cout << "b--->" << a << endl;
printf("a:%d\n", a);
printf("b:%d\n", b);
printf("&a:%d\n", &a);
printf("&b:%d\n", &b);
system("pause");
return 0;
}
结果为:
案例2
#include <iostream>
using namespace std;
struct Teacher
{
char name[64];
int age;
};
//void printTeacher(const Teacher * const myt)
void printTeacher(const Teacher &myt)
{
//常引用 让 实参变量 拥有只读属性
//myt.age = 33;
printf("myt.age:%d \n", myt.age);
}
void main()
{
Teacher t1;
t1.age = 36;
printTeacher(t1);
cout << "hello..." << endl;
system("pause");
return;
}
结果为:
2 使用字面量常量初始化const引用
思考:
1、用变量对const引用初始化,const引用分配内存空间了吗?
2、用常量对const引用初始化,const引用分配内存空间了吗?
void main()
{
const int b = 10;
printf("b:%d", &b);
//int &a1 = 19; 如果不加const编译失败
const int &a = 19;
printf("&a:%d \n", &a);
system("pause");
}
3 综合案例
void main()
{
//普通引用
int a = 10;
int &b = a;
//常量引用 :让变量引用只读属性
const int &c = a;
//常量引用初始化 分为两种
//1 用变量 初始化 常量引用
{
int x = 20;
const int& y = x;
printf("y:%d \n", y);
}
//2 用常量 初始化 常量引用
{
//int &m = 10; //引用是内存空间的别名 字面量10没有内存空间 没有方法做引用
const int &m = 10;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
结果为:
3 const引用结论
1)Const & int e 相当于 const int * const e
2)普通引用 相当于 int *const e
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
4)使用字面量对const引用初始化后,将生成一个只读变量
4const修饰类
后续课程介绍
5综合练习
#include <iostream>
using namespace std;
int& j()
{
static int a = 0;
return a;
}
int& g()
{
int a = 0;
return a;
}
int main()
{
int a = g();
int& b = g();
j() = 10;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("f() = %d\n", j());
system("pause");
return 0;
}
结果为:
7 C++对C的函数扩展
1 inline内联函数
C++中的const常量可以替代宏常数定义,如:
const int A = 3;
define A 3
C++中是否有解决方案替代宏代码片段呢?(替代宏代码片段就可以避免宏的副作用!)
C++中推荐使用内联函数替代宏代码片段
C++中使用inline关键字声明内联函数
内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。
//宏替换和函数调用区别
#include "iostream"
using namespace std;
#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;
}
当使用内敛时,结果为:
当使用宏定义的时候,结果为:
说明1:
必须inline int myfunc(int a, int b)和函数体的实现,写在一块
说明2
C++编译器可以将一个函数进行内联编译,被C++编译器内联编译的函数叫做内联函数,内联函数在最终生成的代码中是没有定义的
C++编译器直接将函数体插入在函数调用的地方 ,内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
说明3:C++编译器不一定准许函数的内联请求!那时候即使用inline修饰了也不一定为内敛
说明4
内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
内联函数是对编译器的一种请求,因此编译器可能拒绝这种请求
内联函数由 编译器处理,直接将编译后的函数体插入调用的地方
而宏代码片段由预处理器处理, 进行简单的文本替换,没有任何编译过程
说明5:
现代C++编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译
另外,一些现代C++编译器提供了扩展语法,能够对函数进行强制内联
如:g++中的attribute((always_inline))属性
说明6:
C++中内联编译的限制:
不能存在任何形式的循环语句;
不能存在过多的条件判断语句;
函数体不能过于庞大;
不能对函数进行取址操作;
函数内联声明必须在调用语句之前;
编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。
因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。
结论:
1)内联函数在编译时直接将函数体插入函数调用的地方
2)inline只是一种请求,编译器不一定允许这种请求
3)内联函数省去了普通函数调用时压栈,跳转和返回的开销
2 默认参数
#include <iostream>
using namespace std;
void myPrint(int x = 3)
{
cout << "x" << x << endl;
}
//1 若 你填写参数,使用你填写的,不填写默认
//2 在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数
void myPrint2(int m, int n, int x = 3, int y = 4)
//void myPrint2( int m, int n, int x = 3, int y ) //会报错
{
cout << "x" << x << endl;
}
void main()
{
myPrint(4);
myPrint();
//
cout << "hello..." << endl;
system("pause");
return;
}
结果为:
3 函数占位参数
占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数
#include<iostream>
using namespace std;
//函数占位参数 函数调用是,必须写够参数
void func1(int a, int b, int)
{
cout << "a" << a << " b" << b << endl;
}
void main()
{
//func1(1, 2); //err调用不起来
func1(1, 2, 3);
cout << "hello..." << endl;
system("pause");
return;
}
结果为:
4 默认参数和占位参数一起用
/*
可以将占位参数与默认参数结合起来使用
意义
为以后程序的扩展留下线索
兼容C语言程序中可能出现的不规范写法
*/
#include<iostream>
using namespace std;
//默认参数和占位参数
void func2(int a, int b, int = 0)
{
cout << "a=" << a << ";b=" << b << endl;
}
void main()
{
func2(1, 2); //0k
func2(1, 2, 3); //ok
cout << "hello..." << endl;
system("pause");
return;
}
结果为