1、C++对C的扩展
1简单的C++程序
//02_简单的C++程序helloworld
#include <iostream>//包含c++的头文件
using namespace std;//使用命名空间std标准的命名空间(在这个命名空间中定义了很多标准定义)
int main()
{
//cout 标准输出 黑屏幕
//<< 左移操作符 在c++里面 功能的改造(增强)===>c++语言操作符重载
//endl \n
//打印到屏幕 并且回车换行
cout << "hell0..." << endl;
system("pause");
return 0;
}
1.1求圆的周长和面积
数据描述:
半径,周长,面积均用实型数表示
数据处理:
输入半径 r;
计算周长 = 2*π*r ;
计算面积 = π* r2 ;
输出半径,周长,面积;
//求圆的面积
//用面向过程的方法求解圆形的面积
//<< >> 不会用:标准输入cin 和标准输出始终写在操作符的左边
void main02()
{
double r = 0;
double s = 0;
cout << "请输入圆形的半径:";
//cin 标准输入 代表键盘中输入
cin >> r;
cout << "r的值是:" << r << endl;
s = 3.14*r*r;
cout << "圆形的面积s:" << s << endl;
system("pause");
}
//在c++中 自定义数据类型
typedef struct Circle
{
double m_s;//圆形的面积
double m_r;//圆形的半径
}Circle;
class MyCircle
{
public:
double m_s;//圆形的面积
double m_r;//圆形的半径
public:
void SetR(double r)//成员函数
{
m_r = r;
}
double getR()//成员函数
{
return m_r;
}
double getS()
{
m_s = 3.14*m_r*m_r;
return m_s;
}
private:
};
void main03()
{
MyCircle c1, c2, c3;//用类定义 变量 对象
double r = 0;
cout << "请输入c1圆形的半径:";
cin >> r;
//给c1圆形的半径属性赋值
c1.SetR(r);
cout << "c1的圆形面积:" << c1.getS() << endl;
}
//用面向对象的方法
//1 类的抽象 成员变量和成员函数
//2 实例化
//3 求面积 面向过程加工的是一个一个函数 面向对象加工是一一个类
//4 main函数集成测试
//思考1:类的调用 执行过程分析:类代码不是一步一步指向
//类是一个数据类型(固定大小内存块的别名);定义一个类,是一个抽象的概念,不给你分配内存
//用数据类型定义变量的时候,才会分配内存
//思考2:c++编译器是如何处理多个对象 调用类的成员函数的,具体一点:如何区分c1,c2,c3调用成员函数getS()
void main()
{
MyCircle c1, c2, c3;//用类定义 变量 对象
double r1 = 0,r2 = 0;
//圆形1
cout << "请输入c1圆形的半径:";
cin >> r1;
//给c1圆形的半径属性赋值
c1.SetR(r1);
cout << "c1的圆形面积:" << c1.getS() << endl;
//圆形2
cout << "请输入c1圆形的半径:";
cin >> r2;
//给c1圆形的半径属性赋值
c2.SetR(r2);
cout << "c2的圆形面积:" << c2.getS() << endl;
system("pause");
}
1.2初学者易犯错误模型
#include <iostream>
using namespace std;
//这个代码在vs10中编译不通过 vs13中编译通过执行乱码:
class MyCircle
{
public:
double r;
double pi = 3.14;
double area = pi*r*r;
double getS()//成员函数
{
area = pi*r*r;
return area;
}
};
void main()
{
MyCircle c1; //此时 r area = pi*r*r;中的r都是随机分配 area = pi*r*r;在声明的时候已经执行完了 此时area代表的内存空间里面存放的是一个垃圾值Z
cout << "请输入c1圆的半径:";
cin >> c1.r;
//虽然r赋值了 但是area = pi*r*r;并没有执行 因为 double area = pi*r*r; 在声明初始化的时候已经执行了 此中的r是一个随机值
//结果:造成area变量是一个乱码
//当输出c1.area的时候只是从变量标示的内存空间中拿值 值已经在声明的时候确定
cout << "c1圆形的面积是:" << c1.area << endl;//乱码 所以此时取出来的值还是系统随机分配的值
cout << c1.getS() << endl;
cout << "c1圆形的面积是:" << c1.area << endl;
system("pause");
}
总结: 从内存四区的角度,解释为什么会出现乱码
理解为什么需要成员函数
2程序设计方法的发展历程
面向过程的结构化程序设计方法
l 设计思路
– 自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。
l 程序结构:
– 按功能划分为若干个基本模块,形成一个树状结构。
– 各模块间的关系尽可能简单,功能上相对独立;每一模块内部均是由顺序、选择和循环三种基本结构组成。
– 其模块化实现的具体方法是使用子程序。
l 优点:
有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子任务,便于开发和维护。
l 缺点:可重用性差、数据安全性差、难以开发大型软件和图形界面的应用软件
– 把数据和处理数据的过程分离为相互独立的实体。
– 当数据结构改变时,所有相关的处理过程都要进行相应的修改。
– 每一种相对于老问题的新方法都要带来额外的开销。
– 图形用户界面的应用程序,很难用过程来描述和实现,开发和维护也都很困难。
面向对象的方法
l 将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。
l 对同类型对象抽象出其共性,形成类。
l 类通过一个简单的外部接口,与外界发生关系。
l 对象与对象之间通过消息进行通信。
面向对象的基本概念
对象
l 一般意义上的对象:
– 是现实世界中一个实际存在的事物。
– 可以是有形的(比如一辆汽车),也可以是无形的(比如一项计划)。
– 是构成世界的一个独立单位,具有
l 静态特征:可以用某种数据来描述
l 动态特征:对象所表现的行为或具有的功能
l 面向对象方法中的对象:
– 是系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。对象由一组属性和一组行为构成。
– 属性:用来描述对象静态特征的数据项。
– 行为:用来描述对象动态特征的操作序列。
类
l 分类——人类通常的思维方法
l 分类所依据的原则——抽象
– 忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的概念。
– 例如,石头、树木、汽车、房屋等都是人们在长期的生产和生活实践中抽象出的概念。
l 面向对象方法中的"类"
– 具有相同属性和服务的一组对象的集合
– 为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。
– 类与对象的关系:
犹如模具与铸件之间的关系,一个属于某类的对象称为该类的一个实例。
封装
也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
l 把对象的属性和服务结合成一个独立的系统单元。
l 尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的对外接口使之与外部发生联系。
l 继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原因之一。
l 定义:特殊类的对象拥有其一般类的全部属性与服务,称作特殊类对一般类的继承。
l 例如:将轮船作为一个一般类,客轮便是一个特殊类。
多态
多态是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。
面向对象的软件工程
l 面向对象的软件工程是面向对象方法在软件工程领域的全面应用。它包括:
– 面向对象的分析(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.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关键字。 usingstd::cout; using std::endl; using std::cin; 以上程序可以写成 cout<< std::hex << 3.4 << endl;
3、最方便的就是使用usingnamespace 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>
using namespace std;
//1 文件中iostream没有引入标准的std 需要我们程序员手工写
//2 如果不写 using namespace std;需要显示的引入std
void main01()
{
cout << "name spacename" << endl;
//std::cout << "name spacename" << std::endl;
system("pause");
}
//3 定义命名空间
namespace namespaceA
{
int a = 10;
}
namespace namespaceB
{
int a = 20;
namespace namespaceC
{
struct Teacher
{
char name[32];
int age;
};
}
}
//4 使用命名空间
//
void main()
{
using namespace namespaceA;
using namespace namespaceB;
cout << namespaceA::a << endl;
cout << namespaceB::a << endl;
//显示的, 写全
{
namespaceB::namespaceC::Teacher t1;
t1.age = 33;
printf("t1.age = %d\n", t1.age);
}
//
using namespaceB::namespaceC::Teacher;
Teacher t2 = { "aaa", 3 };
printf("t2.name = %s\n", t2.name);
printf("t2.age = %d\n", t2.age);
system("pause");
}
4 结论
1) 当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std ,需要这样做。std::cout。
2) c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。
3) C++命名空间的定义: namespacename { … }
4) using namespace NameSpaceA;
5) namespce定义可嵌套。
#include <iostream>
using namespace std;//using声明
//1 文件中iostream没有引入标准的std 需要我们程序员手工写
//2 如果不写 using namespace std;需要显示的引入std
void main01()
{
cout << "name spacename" << endl;
//std::cout << "name spacename" << std::endl;
system("pause");
}
//3 定义命名空间
namespace namespaceA
{
int a = 10;
}
namespace namespaceB
{
int a = 20;
namespace namespaceC
{
struct Teacher
{
char name[32];
int age;
};
}
}
//4 使用命名空间
//
void main04()
{
using namespace namespaceA;
using namespace namespaceB;
cout << namespaceA::a << endl;
cout << namespaceB::a << endl;
//显示的, 写全
{
namespaceB::namespaceC::Teacher t1;
t1.age = 33;
printf("t1.age = %d\n", t1.age);
}
//
using namespaceB::namespaceC::Teacher;
Teacher t2 = { "aaa", 3 };
printf("t2.name = %s\n", t2.name);
printf("t2.age = %d\n", t2.age);
system("pause");
}
//using声明格式为:using 加限定修饰的名字空间成员(using std::string),
//using指示符格式为using namespace 名字空间名(using namespace std)。
//前者保留了该名字空间域,但是将成员名字与一个局部同义词相关联;
//后者是把该名字空间的所有成员转移到包含该名字空间定义的那个域中,其效果相当于去掉了该名字空间
//还有一个重要区别就是如果引起二义性错误,
//前者在声明点就可以检测到,
//而后者一定要在该名字空间成员被使用是才能被检测到
namespace A
{
int i, j;
}
int i;
void fun()
{
using a::i; //相当于在这个局部域中定义int i;,故这个局部域中不能出现类似于int i;的重复定义。全局域中的 int i;被屏蔽
}
void fun()
{
using namespace A; //相当于全局域中的那个名字空间A不存在
i = 6; //error!,二义性,是A::i,还是::i,但是可以通过限定修饰达到目的
::i = 6; //right;
A::i = 6; //right
j = 6; //right!A::i
}
#include <string>
#include <iostream>
using std::cout;
//operator<<函数是定义在标准库string中
//string定义在命名空间std中
//但是我们没有使用std::限定符或者using声明就可以直接调用operator << 这是为什么呢?
int main(){
std::string myStr = "Hello";
cout << myStr;//等价于operator<<(std::cout,myStr);
system("pause");
return 0;
}
argument-dependent lookup或Koening lookup法则
彩蛋来了,这是因为有这样一条规则存在: 当我们给函数传递一个类类型的对象时,首先会在常规的作用域查找,其次在实参类所属的命名空间查找。提示:对于一个未声明的类或函数 以友元的身份,第一次出现在友元声明中,则我们认为它是最近的外层命名空间的成员。这条规则与上述规则一起,会产生意想不到的效果。
友元声明与实参相关的查找
namespace A{
class C{
friend void f2(); //除非另有声明,否则不会被找到
friend void f(const C&); //根据实参相关的查找,就可以被找到
};
}
int main(){
A::C cobj;
f(cobj); //正确,通过在A::C中的友元声明找到A::f
f2(); //错误,A::f2没有被声明
//因为f接受了一个类类型的实参,而且f在C所属的命名空间进行了隐式的声明,所以f能被找到。相反,因为f2没有类类型的形参,所以它无法被找到。
}
using声明 using std::cin;
using 指示 using namespace std;
1)using声明一次只能引入一个特定命名空间的成员。而且它的作用域是从声明点开始,直到包含该using声明作用域的末尾,外部作用域的同名实体被屏蔽(但是如果在同一作用域有同名实体,则会出现错误)。实际上一个using声明就如命名空间成员的局部别名一样。#ifndef LOCATION_H_INCLUDED
#define LOCATION_H_INCLUDED
namespace MySpace{
int i=1;
int j=1;
int k=1;
}
#endif // LOCATION_H_INCLUDED
#include"location.h"
using namespace std;
int i=10;
int main()
{
using MySpace::i;//覆盖全局变量i=10,使i=1
int i=20; //错误,与int MySpace::i冲突
return 0;
}
2)using指示使得特定命名空间的所有成员可见。没有任何限制,它将命名空间成员的作用域提升到包含命名空间本身和using指示的最近作用域。
#include"location.h"
using namespace std;
int i=10;
int main()
{
using namespace MySpace;
i++;//错误,二义性
::i++; //正确::i=11
MySpace::i++;//正确MySpace::i++=2
j++; //正确j=2
int k=10;//正确
k++; //k=11
return 0;
}
比如在这个例子中,MySpace命名空间定义在全局作用域中,在main函数中看来,MySpace中的成员就好像声明在全局作用域中。所以在使用变量 "i" 的时候会出现二义性的错误,因为分不清"i"到底是来自哪个作用域(全局作用域还是MySpace中?)。而局部变量k则屏蔽命名空间的成员MySpace::k,这一点与(1)中的int i=20是不同的。
总之using声明是声明一个局部别名,而using指示是将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属的作用域函数同名,则命名空间的函数被添加到重载集合中
4.2 “实用性”增加
#include "iostream"
using namespace std;
//C语言中的变量都必须在作用域开始的位置定义!!
//C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。
int main()
{
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也可能做优化
C++中可以取得register变量的地址
//2
C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。
//3
早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。
*/#include "iostream"
using namespace std;
int main()
{
register int a = 0;
printf("&a = %x\n", &a);
//&a = 34fc60
// 请按任意键继续. . .
system("pause");
return 0;
}
4.4变量检测增强
#include "iostream"
using namespace std;
/*
在C语言中,重复定义多个同名的全局变量是合法的
在C++中,不允许定义多个同名的全局变量
C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
int g_var;
int g_var = 1;
C++直接拒绝这种二义性的做法。
*/
int g_var;
int g_var = 1;
int main(int argc, char *argv[])
{
printf("g_var = %d\n", g_var);//error C2086: “int g_var”: 重定义
return 0;
}
4.5 struct类型加强
//区别:struct类型的加强(C++版本)
#include<iostream>
using namespace std;
//C++中的struct是一个新类型的定义声明
//c++中结构体的默认访问权限public,和类不同
//struct默认是public
//class默认是private
struct Teacher{
//char name[20]="小米";//报错 error C2536: “Teacher::Teacher::name”: 无法指定数组的显式初始值设定项
//错误原因:上面这种初始化,同时完成了内存空间的分配和内容的初始化。
//但是结构体在类型定义的时候并没有分配内存,所以报错,对于int age=1;可能是c++编译器做了特殊处理
char name[20];
int age = 1;//在c++中,结构体定义时部分成员变量可以赋初始值(为了和C语言统一,还是别用这种写法了,结构体声明中不要做任何赋值操作)
//c++可以在结构体内定义函数,C语言不可以
int getage(){
return age;
}
int r;
//int s = r*r; 不要做这种危险的定义,因为当定义Teacher变量的时候,便会进行r*r的运算,此时r可能还没有赋值
//此时的s就是个脏数据,如果后来你需要使用s,此时s不会再次进行r*r的运算,导致你永远取不到正确的s的值
//最好的办法写一个成员函数
int gets(){
return r*r;
}
};
void main(){
//在c++定义结构体,可以使用struct关键字,也可以不使用
Teacher t2;
struct Teacher t1;
printf("age=%d\n", t1.age);
system("pause");
}
4.6 C++中所有的变量和函数都必须有类型
//区别:struct类型的加强(C++版本)
#include<iostream>
using namespace std;
/*
C++中所有的变量和函数都必须有类型
C语言中的默认类型在C++中是不合法的
函数f的返回值是什么类型,参数又是什么类型?
函数g可以接受多少个参数?
*/
//更换成.cpp试试
void f(int i)
{
printf("i = %d\n", i);
}
int g(int a,int b,int c,int d,int e)
{
return 5;
}
int main(int argc, char *argv[])
{
f(10);
printf("g() = %d\n", g(1, 2, 3, 4, 5));
getchar();
return 0;
}
总结:
/*
在C语言中
intf( );表示返回值为int,接受任意参数的函数
intf(void);表示返回值为int的无参函数
在C++中
intf( );和int f(void)具有相同的意义,都表示返回值为int的无参函数
*/
C++更加强调类型,任意的程序元素都必须显示指明类型
4.7新增Bool类型关键字
//区别:struct类型的加强(C++版本)
#include<iostream>
using namespace std;
/*
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;
}
4.8三目运算符功能增强
1三目运算符在C和C++编译器的表现
#include<iostream>
using namespace std;
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中的三目运算法当左值呢?
5 C/C++中的const
1 const基础知识(用法、含义、好处)
#include<iostream>
using namespace std;
//0 const基础知识
struct Teacher
{
char name[64];
int age;
};
//指针所指向的内存空间 不能被修改
int operatorTeacher01(const Teacher *pT)
{
//pT->age = 10;
pT = NULL;//指针变量本身能被修改
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;//指针变量本身不能被修改
return 0;
}
void main01()
{
// const int a;
// int const b; //一样 代表一个常整型数
// const int *c; //const修饰的是指针所指向的内存空间,不能被修改
// int * const d; //指针变量不能被修改,但是它所指向内存空间可以被修改
// const int * const e ;//指针和它所指向的内存空间,均不能被修改
cout << "hello..." << endl;
Teacher t1;
t1.age = 33;
operatorTeacher03(&t1);
system("pause");
return;
}
//1
//c语言中的const是一个冒牌货
//C++语言中 const是一个真正的常量
//2 原因分析
//const int b = 10; 符号表
void main02()
{
//好像 a 是一个常量
const int a = 10;//这个a放在符号表中 对于key value
//a = 11;
int *p = NULL;
p = (int *)&a;//当你对a变量取地址时候,c++会单独开辟内存空间,对这个空间赋值,而a符号表key - value依然不变
*p = 20; //间接赋值 但是这个时候
printf("a:%d \n", a);//10
printf("*p:%d \n", *p);//20
system("pause");
}
//3 const分配内存的时机 编译器编译器期间
void main03()
{
//好像 a 是一个常量
int a;
const int b = 10;
int c;
printf("&a:%d, &b:%d, &c:%d \n", &a, &b, &c);
system("pause");
}
//4const 和 #define的相同之处
//#define, 在编译预处理阶段 处理
//const常量是由编译器处理的,提供类型检查和作用域检查
#define d 20
void main04()
{
int a = 10;
int b = 20;
//int array[a + b];//linux内核里面是成立的;原因 编译linux内核的gcc编译器支持.
//c和c++编译器 不支持这种语法现象
const int c = 10;
/*const int d = 20;*/
int array2[c + d];
system("pause");
}
//5 const定义的变量,由编译器处理的,提供类型检查和作用域检查
void fun1()
{
#define e 10;
const int f = 20;
//#undef e
//# undef
}
void fun2()
{
printf("e = %d\n", e);
printf("f = %d\n", f);
}
int main()
{
fun1();
fun2();
return 0;
}
2 C中“冒牌货”
C++中const符号表原理图
注意: C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。 |
结论: C语言中的const变量 C语言中const变量是只读变量,有自己的存储空间 C++中的const常量 可能分配存储空间,也可能不分配存储空间 当const常量为全局,并且需要在其它文件中使用 当使用&操作符取const常量的地址
|
3 const和#define相同之处
C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了。4 const和#define的区别
对比加深
C++中的const常量类似于宏定义
const int c = 5; ≈ #define c 5
C++中的const常量与宏定义不同
const常量是由编译器处理的,提供类型检查和作用域检查
宏定义由预处理器处理,单纯的文本替换
5 结论
C语言中的const变量
C语言中const变量是只读变量,有自己的存储空间
C++中的const常量
可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其它文件中使用,会分配存储空间
当使用&操作符,取const常量的地址时,会分配存储空间
当const int &a = 10; const修饰引用时,也会分配存储空间
6引用专题讲座
1引用(普通引用)
变量名回顾
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间
问题1:对一段连续的内存空间只能取一个别名吗?
1 引用概念
a) 在C++中新增加了引用的概念
b) 引用可以看作一个已定义变量的别名
c) 引用的语法:Type& name = var;
d) 引用做函数参数那?(引用作为函数参数声明时不进行初始化)
2 引用是C++的概念
属于C++编译器对C的扩展
3 引用做函数参数
#include <iostream>
using namespace std;
int main0901()
{
int a = 10;
//引用的语法:Type& name = var;
int &b = a;
b = 100;//相当于把a修改成100
cout << "a:" << a << endl;
cout << "b:" << b << endl;
a = 200;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "hello..." << endl;
system("pause");
return 0;
}
void main0902()
{
int a = 10;
int &b = a;
//int &c;//普通引用 必须要初始化
}
//基础类型的引用
void mySwap(int a, int b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void mySwap02(int *a,int *b)
{
int c = 0;
c = *a;
*a = *b;
*b = c;
}
//引用作为函数参数声明时不进行初始化
void mySwap03(int &a,int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void main0903()
{
int x, y;
x = 10; y = 20;
mySwap(x,y);
cout << "x:" << x << "y:" << y << endl;
mySwap02(&x,&y);
cout << "x:" << x << "y:" << y << endl;
//a就是x的别名 b就是y的别名
mySwap03(x,y);
cout << "x:" << x << "y:" << y << endl;
//int &c ; //普通引用 必须要初始化
system("pause");
}
//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()
{
Teacher t1;
t1.age = 35;
printfT(&t1);//35
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); //33
cout << "hello..." << endl;
system("pause");
return;
}
4 引用的意义
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针 2)引用相对于指针来说具有更好的可读性和实用性 |
#include <iostream>
using namespace std;
//5 引用本质思考
//1 单独定义的引用时,必须初始化,说明很像一个常量
void main1001()
{
const int c1 = 10;
int a = 10;
int &b = a;//像一个常量
printf("&a:%d \n", &a);
printf("&b:%d \n", &b); //===> a 和 b就是同一块内存空间的门牌号
//&a:3078720
//&b:3078720
//hello...
//请按任意键继续. . .
cout << "hello..." << endl;
system("pause");
return;
}
//2 普通引用有自己的空间吗? 有
struct Teacher
{
char name[64];//64
int age;//4
double &a;//4 ===>很像指针 所占的内存空间大小
int &b;//4
};
void main1002()
{
printf("sizeof(Teacher):%d\n",sizeof(Teacher));
system("pause");
}
//3 引用的本质
//引用在C++中的内部实现是一个常指针
//Type& name Type* const name
//C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同
//这两个是等效的函数 ,只不过调用使用了引用的函数不用手工取地址,C++内部会自动取地址,但是使用指针的函数,需要我们手动取地址。
void modifyA(int &a)
{
a = 100;
}
void modifyB(int *const a)
{
*a = 90;//实参的地址 去间接的修改实参的值
}
void main1003()
{
int a = 10;
modifyA(a);//执行这个函数,程序员不需要取a的地址
cout << a << endl;//100
modifyB(&a);//执行这个函数,程序员不需要取a的地址
cout << a << endl;//90
system("pause");
}
//间接赋值
//4) 请仔细对比间接赋值成立的三个条件
//1定义两个变量 (一个实参一个形参)
//2建立关联 实参取地址传给形参
//3 * p形参去间接的修改实参的值
//应用场景:1 2写一块 3调用
void modifyC(int *p)
{
*p = 23;//3 *p 形参间接去修改实参的值
}
//引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一
void main()
{
int a = 10;
int *p = NULL;//1 定义两个变量 (一个实参一个形参)
p = &a;
*p = 12;
modifyC(&a);//2 建立关联 实参取地址传给形参
system("pause");
}
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; } |
单独定义的引用时,必须初始化;说明很像一个常量 |
思考2:普通引用有自己的空间吗?
struct Teacer { int &a; int &b; }; int main() { printf("sizeof(Teacher) %d\n", sizeof(Teacer)); system("pause"); return 0; } |
引用是一个有地址,引用是常量。。。。。 char *const p
|
6 引用的本质
1)引用在C++中的内部实现是一个常指针
Type& name çèType*const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
Int main()
{
int x = 10;
func(x);
}
4) 请仔细对比间接赋值成立的三个条件
1定义两个变量(一个实参一个形参)
2建立关联实参取地址传给形参
3*p形参去间接的修改实参的值
7引用结论
1)引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一
//当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)
2)当我们使用引用语法的时,我们不去关心编译器引用是怎么做的
当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的
8函数返回值是引用(引用当左值)
C++引用使用时的难点:
当函数返回值为引用时
若返回栈变量
不能成为其它引用的初始值
不能作为左值使用
若返回静态变量或全局变量
可以成为其他引用的初始值
即可作为右值使用,也可作为左值使用
C++链式编程中,经常用到引用,运算符重载专题
返回值是基础类型,当引用
#include <iostream>
using namespace std;
int getA()
{
int a;
a = 10;
return a;
}
//返回一个引用
int& getA2()
{
int a;//如果返回栈上的引用 可能会有问题
a = 20;
return a;
}
//返回一个指针
int* getA3()
{
int a;
a = 30;
return &a;
}
void main()
{
int a1 = getA();
int a2 = getA2();
//C++引用使用时的难点:
//当函数返回值为引用时
//若返回栈变量
//不能成为其它引用的初始值
//不能作为左值使用
int &a3 = getA2();
printf("a1:%d \n", a1);
printf("a2:%d \n", a2);
printf("a3:%d \n", a3); // *a3
cout << "hello..." << endl;
system("pause");
return;
}
#include <iostream>
using namespace std;
//变量时static或者全局变量
int j1()
{
static int a = 10;
a++;
return a;
}
int& j2()
{
static int a = 10;
a++;
printf("%d",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");
}
///
//--函数当左值
//返回变量的值
int g1()
{
static int a = 10;
a++;
return a;
}
//返回变量本身
int& g2()
{
static int a = 11;
a++;
printf("a:%d\n",a);
return a;
}
void main()
{
//g1() = 100;
//11 = 100;
g2() = 100; //函数返回值是一个引用,并且当左值
g2();
int c1 = g1();//函数返回值是一个引用,并且当右值
int c2 = g2();//函数返回值是一个引用,并且当右值
//a=100;
system("pause");
}
#include <iostream>
using namespace std;
//指针的引用
struct Teacher
{
char name[64];
int age;
};
//被调用函数 获取资源
int getTeacher(Teacher **p)
{
Teacher *tmp = NULL;
if (NULL == p)
{
return -1;
}
tmp = (Teacher *)malloc(sizeof(Teacher));
if (NULL == tmp)
{
return -2;
}
tmp->age = 33;
//p是实参的地址 *实参的地址 去间接修改实参的值
*p = tmp;
return 0;
}
//指针的引用 做函数参数
int getTeacher2(Teacher* &myp)
{
//给myp赋值 相当于给main函数中的pT1赋值
myp = (Teacher *)malloc(sizeof(Teacher));
if (myp==NULL)
{
return -1;
}
myp->age = 37;
return 0;
}
void FreeTeacher(Teacher *pT1)
{
if (pT1 == NULL)
{
return;
}
free(pT1);
}
void main()
{
Teacher *pT1 = NULL;
//c语言中的二级指针
getTeacher(&pT1);
cout << pT1->age << endl;
FreeTeacher(pT1);
//2 c++中的引用(指针的引用)
//引用的本质 间接赋值后2个条件 让c++编译器帮我们程序员做了。
getTeacher2(pT1);
cout << pT1->age << endl;
system("pause");
}
#include <iostream>
using namespace std;
//常引用的知识架构
void main1301()
{
//普通引用
int a = 10;
int &b = a;
cout << b << endl;
//常引用
int x = 20;
const int &y = x;//常引用 是让变量 引用只读属性 不能通过y去修改x了
//常引用 初始分为2种情况
//用变量初始化常引用
{
int x1 = 20;
const int &y1 = x1;//用x1变量去初始化 常引用
}
//2 用字面量 初始化 常量引用
{
const int a = 40;//c++编译器把a放在符号表中
//int &m = 41;//普通引用 引用一个字面量 请问字面量有没有内存地址
//引用是给内存取别名,字面值没有内存地址 就是给内存取多个门牌号 (多个别名)
//printf("&40:%d",4&0);
const int &m = 43; //c++编译器 会 偷偷给m分配内存空间 指向这个43
printf("&m:%d",m);
}
system("pause");
};
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;
}
3 const引用结论
1)Const &int e 相当于 const int * conste
2)普通引用 相当于 int *const e1
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
4)使用字面量对const引用初始化后,将生成一个只读变量
7C++对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); //头疼系统 int c = MYFUNC(++a, b);
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++编译器不一定准许函数的内联请求! 说明4 内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等) 内联函数是对编译器的一种请求,因此编译器可能拒绝这种请求 内联函数由 编译器处理,直接将编译后的函数体插入调用的地方 宏代码片段 由预处理器处理, 进行简单的文本替换,没有任何编译过程 |
说明5: 现代C++编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译 另外,一些现代C++编译器提供了扩展语法,能够对函数进行强制内联 如:g++中的__attribute__((always_inline))属性 |
说明6: C++中内联编译的限制: 不能存在任何形式的循环语句 不能存在过多的条件判断语句 函数体不能过于庞大 不能对函数进行取址操作 函数内联声明必须在调用语句之前 |
编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。 因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。 |
结论: 2)inline只是一种请求,编译器不一定允许这种请求 3)内联函数省去了普通函数调用时压栈,跳转和返回的开销 |
#include <iostream>
using namespace std;
inline void printA()
{
int a = 10;
cout << "a" << a << endl;
}
int main()
{
printA();
//C++编译器直接将函数体插入在函数调用的地方
//{
// int a = 10;
// cout << "a" << a << endl;
//}
cout << "hello..." << endl;
system("pause");
return 0;
}
//内联函数声明时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) : (b)) ==>(++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;
}
2 默认参数
/*1 C++中可以在函数声明时为参数提供一个默认值, 当函数调用时没有指定这个参数的值,编译器会自动用默认值代替 */ |
void myPrint(int x = 3) { printf("x:%d", x); } /*2 函数默认参数的规则 只有参数列表后面部分的参数才可以提供默认参数值 一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数值 */
|
//默认参数 void printAB(int x = 3) { printf("x:%d\n", x); }
//在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数 void printABC(int a, int b, int x = 3, int y=4, int z = 5) { printf("x:%d\n", x); } int main62(int argc, char *argv[]) { printAB(2); printAB(); system("pause"); return 0; } |
3 函数占位参数
/* 函数占位参数 占位参数只有参数类型声明,而没有参数名声明 一般情况下,在函数体内部无法使用占位参数 */ |
int func(int a, int b, int ) { return a + b; }
int main01() { //func(1, 2); //可以吗? printf("func(1, 2, 3) = %d\n", func(1, 2, 3));
getchar(); return 0; }
|
4 默认参数和占位参数
/* 可以将占位参数与默认参数结合起来使用 意义 为以后程序的扩展留下线索 兼容C语言程序中可能出现的不规范写法 */ //C++可以声明占位符参数,占位符参数一般用于程序扩展和对C代码的兼容 |
int func2(int a, int b, int = 0) { return a + b; } void main() { //如果默认参数和占位参数在一起,都能调用起来 func2(1, 2); func2(1, 2, 3); system("pause"); } |
结论://如果默认参数和占位参数在一起,都能调用起来 |
//默认参数
#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) 像这么写就不太对,从int x=3后面就必须都带上默认参数
{
cout << "y:" << y << endl;
}
void main1501()
{
myPrint(4);
myPrint();
cout << "hello..." << endl;
system("pause");
return;
}
//函数占位参数 函数调用是,必须写够参数
/*
函数占位参数
占位参数只有参数类型声明,而没有参数名声明
一般情况下,在函数体内部无法使用占位参数
*/
void func1(int a, int b, int)
{
cout << "a:" << a << "b:" << b << endl;
}
//
void main1502()
{
//func1(1,2);//err调用不起来
func1(1,2,3);
cout << "hello..." << endl;
system("pause");
return;
}
//默认参数和占位参数
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;
}
5 函数重载(Overroad)
函数重载概念
1 函数重载概念 函数重载(Function Overload) 用同一个函数名定义不同的函数 当函数名和不同的参数搭配时函数的含义不同
2 函数重载的判断标准 /* 函数重载至少满足下面的一个条件: 参数个数不同 参数类型不同 参数顺序不同 */ 3 函数返回值不是函数重载的判断标准 实验1:调用情况分析;实验2:判断标准 |
//两个难点:重载函数和默认函数参数混搭 重载函数和函数指针 /* int func(int x) { return x; }
int func(int a, int b) { return a + b; }
int func(const char* s) { return strlen(s); }
int main() { int c = 0;
c = func(1);
printf("c = %d\n", c);
c = func(1, 2);
printf("c = %d\n", c);
c = func("12345");
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */ |
函数重载的调用准则
/* 编译器调用重载函数的准则 将所有同名函数作为候选者 尝试寻找可行的候选函数 精确匹配实参 通过默认参数能够匹配实参 通过默认类型转换匹配实参 匹配失败 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。 无法匹配所有候选者,函数未定义,编译失败。 */
/* 函数重载的注意事项 重载函数在本质上是相互独立的不同函数(静态链编) 重载函数的函数类型是不同的 函数返回值不能作为函数重载的依据 函数重载是由函数名和参数列表决定的。 */ |
函数重载是发生在一个类中里面 |
函数重载遇上函数默认参数
//当函数默认参数遇上函数重载会发生什么 /* int func(int a, int b, int c = 0) { return a * b * c; }
int func(int a, int b) { return a + b; }
//1个参数的允许吗 int func(int a) { return a + b; }
int main() { int c = 0;
c = func(1, 2); // 存在二义性,调用失败,编译不能通过
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */
|
函数重载和函数指针结合
/* 函数重载与函数指针 当使用重载函数名对函数指针进行赋值时 根据重载规则挑选与函数指针参数列表一致的候选者 严格匹配候选者的函数类型与函数指针的函数类型 */ /* int func(int x) // int(int a) { return x; }
int func(int a, int b) { return a + b; }
int func(const char* s) { return strlen(s); }
typedef int(*PFUNC)(int a); // int(int a)
int main() { int c = 0; PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
printf("Press enter to continue ..."); getchar(); return 0; } */ |
#include <iostream>
using namespace std;
void myfunc(int a)
{
printf("a:%d \n", a);
}
void myfunc(char *p)
{
printf("%s \n", p);
}
void myfunc(int a, int b)
{
printf("a:%d \n", a);
}
void myfunc(char *p1, char *p2)
{
printf("p1:%s ", p1);
printf("p2:%s \n", p2);
}
//函数指针 基础的语法
//1声明一个函数类型
typedef void (myTypeFunc)(int a, int b); //int
//myTypeFunc *myfuncp = NULL; //定义一个函数指针 这个指针指向函数的入口地址
//声明一个函数指针类型
typedef void(*myPTypeFunc)(int a, int b); //声明了一个指针的数据类型
//myPTypeFunc fp = NULL; //通过 函数指针类型 定义了 一个函数指针
//定义一个函数指针 变量
void(*myVarPFunc)(int a, int b);
void main()
{
myPTypeFunc fp;//定义了一个 函数指针变量
fp = myfunc;
//fp(1);
fp(1,2);
myVarPFunc = myfunc;
//{
// char buf1[] = "aaaaafff";
// char buf2[] = "bbbb";
// fp(buf1, buf2);//会严格检查类型是否匹配,函数类型是int
//}
system("pause");
}
附录2:C语言register关键字—最快的关键字
register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。你想想,一个CPU 的寄存器也就那么几个或几十个,你要是定义了很多很多register 变量,它累死也可能不能全部把这些变量放入寄存器吧,轮也可能轮不到你。 一、皇帝身边的小太监----寄存器 不知道什么是寄存器?那见过太监没有?没有?其实我也没有。没见过不要紧,见过就麻烦大了。^_^,大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。这个小太监只是个中转站,并无别的功能。 |
二、举例 register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。例如下面的内存块拷贝代码, #ifdef NOSTRUCTASSIGN memcpy (d, s, l) { register char *d; register char *s; register int i; while (i--) *d++ = *s++; } #endif 但是使用register修饰符有几点限制。 首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。 其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。 由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。 在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。 早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。 |