C++(7-8章)笔记
第七章 函数——C++的编程模块
7.1函数
1,函数如何返回值的?
答:函数通过将返回值复制到指定的cpu寄存器或内存单元中来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据。
2,为什么需要原型?
答:原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型和数量告诉编译器。
3,如果该函数不能修改形参中的数组该如何操作〉
答:可以如下:void show_array(cons tint arr[],int n);
在加上const之后表示该数组无法再sho_array()被修改,但并不是表示该数组中的所有数都是常量。
C++将const int
arr[]解释为const int *arr。因此该声明实际上是说,arr指向的是一个常量值。
第八章 函数探幽
8.1 C++内联函数
1,常规函数和内联函数之间的主要区别不在于编写方式,而是在于C++编译器如何将他们组合到程序中。
2,函数执行时内部的操作方式:
编译器执行到函数调用的指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存你的指令处。
C++内联函数提供了另一种选择。内联函数的编译代码与其他程序“内联”起来。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,然后再跳回来。因此,内联函数执行速度比常规函数稍快,但代价就是消耗更多的内存。如果程序在10个不同的地方调用了同一个内联函数,那程序将包括该函数的10个代码拷贝。
使用:在函数声明前加上关键字inline。函数定义前加上关键字inline。 内联函数不能递归。
8.2 引用变量
1,int rats = 100; int &
rod = rats;
这表示rod引用了rats,这两个变量的地址是相同的。
与指针有所不同在于,指针可以先声明,再赋值。这个必须在初始化时就赋值。 且 rod只能被赋值一次。 引用通常用于函数里面。Rod
= x+3;
这是错误的。
2,如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。
3,应尽可能的使用const。
8.2.4将引用用于结构。P233页
定义函数: const sysop
& use(sysop &sysopref);
1,第一个调用函数演示:ues(looper);
该函数调用将结构体looper按引用传递给use()函数,使得sysopref成为了looper的别名。
2,第二个是将引用作为返回值,通常返回机制将返回值复制到临时存储区域中,随后调用程序将访问该区域。然而,返回引用意味着调用程序将直接访问返回值,而不需要拷贝。通常,引用将指向传递给函数的引用,因此调用函数实际上是直接访问自己的一个变量。例如,在这个例子中,sysopref是looper的引用,因此返回值是main()中的原始looper变量。请看下面一行代码:
copycat = use(looper);
如果函数use()返回一个结构,sysopref的内容将被复制到一个临时返回存储单元中,返回该临时返回存储单元的内容将被复制到copycast中。然而,由于use()返回一个指向looper的引用,在这种情况下,looper的内存将被直接复制到copycast中。这使得执行效率更高。
记住:返回引用的函数实际上是被引用的变量的别名。
3,该程序使用函数调用来访问结构的成员
cout << “ues(looper):”<<use(looper).used<<endl;
由于函数use()返回一个指向looper的引用,因此上述代码与下面等价:
use(looper);cout<<”use(looper)”<<looper.used<<endl;
表示法use(looper).used访问looper的成员used。如果该函数返回一个结构而不是指向结构的引用,则这些代码访问的将是looper的临时拷贝的used成员。
2,返回引用时需要主要的问题
1,应避免返回当函数终止时不再存在的内存单元引用。
2,可采用new来分配新的存储空间。
3,为何将const用于引用返回类型
1,它并不意味着结构体sysop本身为const,而只一位着你不能使用返回的引用来直接修改它指向的结构。如省掉const就有可能出现下面的代码:
Use(looper).used
= 10;这会使设计中添加模糊特性。增加犯错机会。
8.3默认参数
1,在函数调用时,可省掉实参,从而省掉的形参采用默认值
2,在原型中声明:
char *left(const char *str, int n =
1);
3,必须从右向左添加默认值,也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。
8.4函数重载
1,必须参数数目或参数类型不同。
2,名称修饰:
C++如何跟踪每一个重载函数呢?它给这些函数指定了秘密身份。使用C++开发工具中的编译器编写和编译程序时,C++编译器将执行一些神奇的操作——名称修饰或名称矫正。他根据函数原型中指定的形参类型对每个函数名进行加密。如:
long MyFunctionFoo(int,float)。将被名称转换为不太好看的内部表示,来描述该接口,如下所示: ?MyFunctionFoo@@YAXH@Z
对原始名称进行的表面看来无意义的修饰将对参数数目和类型进行编码。
8.5函数模板
1,函数模板就是使用通用类型(可用具体类型int,float替换)来定义函数。
2,函数模板允许以仁义类型的方式来定义函数,例如:
template <class Any>
void Swap(Any &a,Any &b)
{
Any tem; temp = a; a = b; b =
temp;
}
第一行指出要建立一个模板,并将类型令名为Any。关键字template和class是必须的。除非可以使用关键字typename代替class。另外必须使用尖括号。类型名可以任意选择(这里为Any),只要遵守C++令名规则即可;模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器将按模板模式创建这样的函数,并用int代替Any。
提示:如果需要多个将同一种算法用于不同类型的函数,请使用模板,如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename而不使用class。
注意:函数模板不能缩短可执行程序,最终的代码不包括任何模板,而只包括了为程序生成的实际函数。模板的好处在于:生成多个函数定义更简单,更可靠。
8.5.1重载函数模板
1,如果我们定义了Swap的模板,可现在要对两个数组进行交换,我们又应该怎么操作呢?
我们可以重载他,定义void
Swap(Any *a,Any *b,int n);这与重载函数有异曲同工之处。
8.5.2显式具体化
如果我们想当编译器找到与函数调用匹配的具体话定义时,将使用该定义,而不再寻找模板。
1,对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及他们的重载版本。
显式具体化的原型和定义应该以template<>打头,并通过名称来指出类型。
具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。
下面为交换job结构的非模板函数,模板函数和具体化的原型
void Swap(job &,job &);///非模板
template<class Any> ///模板
void Swap(Any &,Any &);
template<>void
Swap<job>(job &,job &);///显式具体化
template<>void Swap(job
&,job &);///simpler form 同显式具体化
8.5.3实例化和具体化
1,语法:template void
Swap<int>(int,int);
实现了这种特性的编译器看到上述声明后,将使用Swap()模板生成一个使用int类型的实例。也就是说,该声明的意思是“使用Swap()模板生成int类型的函数定义”。(函数会有一个Swap函数)
与显式实例化不同的是,显式具体化使用下面两个等价的声明之一:
template<>void
Swap<int>(int &,int&);
template <>void Swap(int &,int &);
(显式具体化只会在程序中如果有调用到此显式才会定义这个函数)
注意:试图在同一个编程单元中使用同一种类型的显式实例和显式具体化将出错。
隐式实例化,显式实例化和显式具体化统称为具体化。他们的相同之处在于,他们表示的都是使用具体类型的函数定义,而不是通用描述。
8.5.4编译器选择使用哪个函数版本
1,编译器决定使用哪个函数,这个过程称为重载解析,
第一步:创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。
第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。
第三步:确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。
如何确定最佳:(最佳到最差顺序如下)
1,完全匹配,但常规函数优先于模板
2,提升转换(char转int)
3,标准转换(int转char)
4,用户定义的转换,如类声明中定义的转换。
第八章学习代码:
///c++第八章 #include"stdio.h" #include"string.h" #include"iostream" #include"algorithm" using namespace std; ///inline表示该函数为内联函数,参数为引用参数 ///同时如果不想对函数参数的引用的值改变可将函数写为square1形式 ///此为work1引用的有关函数 inline int square(int &x) { int &z = x; return z = z * z; } inline int square1(const int &x) { /// int &z = x; ///此语句是错误的,同样在其中对x值进行改变也是错误的。 ///编译器将禁止其对x的值进行改变。 int z = x;///此可行 return x * z; } ///此为临时变量&的有关函数 inline void swapr1(int &a,int &b)///交换a,b值 { int temp = a; a = b; b = a; return ; } inline void swapr2(long long &a,long long &b) { long long temp = a; a = b; b = temp; return ; } inline int swapr3(const int &a,const int &b) { ///鼓励使用const原因如下 ///1,使用const可以避免无意中修改数据的编程错误。 ///2,使用const使函数能够处理const和非const实参。否则将只能接受非const数据。 ///3,使用const引用使函数能够正确生成并使用临时变量 return a + b; } ///此为引用结构体的有关函数及其操作 struct Node{ char name[26]; char quote[26]; int used; }; const Node & use(Node &node) { ///此node为别名,为引用 ///返回值为引用,此为: ///1,返回机制将返回值复制到临时存储区域中,随后调用程序访问该区域。 ///然而,返回引用意味着调用程序将直接访问返回值,而不需要拷贝。 ///通常,引用指向传递给函数的引用,因此调用函数实际上是直接访问自己的一个变量。 ///因不进行拷贝,故速度会快。 ///一定要注意不要返回当函数终止时不再存在的内存单元。 cout << node.name << " says:\n"; cout << node.quote << endl; cout << endl; node.used ++; return node; } ///函数交换模板 template <class Any> void Swap(Any &a,Any &b)///也可写为 template <typename Any> { Any temp; temp = a; a = b; b = temp; } template <typename Any> void Swap(Any &a,Any &b,Any &c) { Any temp; temp = c; c = b; b = a; a = temp; } void work1()///此函数为&时的操作 { printf("这里为引用的相关操作:\n"); int x; scanf("%d",&x); int y = square(x); int &z = x;///引用参数必须进行初始化赋值,同时只能赋值一次,一次引用确定后,就不可再次引用别的变量 ///上代码实际为int * const z = &x;的伪装表示 printf("x = %d y = %d z = %d\n",x,y,z); printf("x = %x y = %x z = %x\n",&x,&y,&z);///x与z的地址相同,y则差一个int单位,印证了他是一个栈 } void work2()///临时匿名变量(引用) { printf("这里为临时你们匿名变量的相关操作:\n"); long long a = 3,b = 4; ///swapr1(a,b);此语句将会报错,因为swapr的函数的参数为int型,无法引用longlong型数据 int x = 5,y = 6; ///swapr2(x,y);同样参数为longlong型,无法引用int型数据 ///引用类型并不会强制转换,原因:当类型不同时, ///编译器会进行隐式转换,创建一个和函数参数相同类型的临时匿名变量 ///然后函数参数的引用,会指向的是那两个临时匿名变量 ///从而导致最后函数参数指向的是临时匿名变量,达不到我们想要的效果 ///此操作在早期c++较宽松时可行。 ///三种临时匿名变量会出现的情况,同时下三种引用必须为const型 int s = swapr3(a,b);///此操作同是int引用longlong变量 ///可行原因在于,函数参数为const型,表示无法更改值。 ///那么在这种情况下,引用指向一个临时匿名变量是可以的 int s1 = swapr3(6,7);///此种调用,同样会出现临时匿名变量,因为6,7无地址,所以需要临时匿名变量 int s2 = swapr3(a + 1,b);///同样此也会出现临时匿名变量,因为a+1为表达式无地址 printf("%lld %lld\n",a,b); printf("%d %d s = %d s1 = %d s2 = %d\n",x,y,s,s1); } void work3()///引用结构体相关操作 { printf("这里为引用结构体的相关操作:\n"); cout << "此函数有三处不同之地" << endl; cout << "1,使用了指向结构的引用,由use(node)展示"<< endl; cout << "2,将引用作为返回值" << endl; cout << "3,使用函数调用来访问结构成员,由use(node).used展示"<< endl; Node node = { "Rick","I LOVE YOU",0 }; use(node); cout << "Node : " << node.used << " use(s)\n"; Node node1; node1 = use(node); cout << "Node : " << node.used << " use(s)\n"; cout << "Node1 : " << node1.used << " use(s)\n"; cout << "use(node): " << use(node).used << " use(s)\n"; ///use的有一个不同之处在于,将const用于引用返回类型。 ///此const的作为本不意味着结构Node本身为const,而只意味着 ///不能使用返回的引用来直接修改它指向的结构。如省掉此const,则可能可以出现如下语句 ///use(node).used = 10;当然此并没有错。 ///等价于use(node); node.used = 10; ///总之,省掉const后,便可以编写更简短但含义更模糊的代码。 return ; } void work4()///函数模板 { int x,y; x = 6; y = 19; Swap(x,y); int a = 1,b = 3,c = 5; Swap(a,b,c); printf("x = %d y = %d\n",x,y); printf("a = %d b = %d c = %d\n",a,b,c); } int main() { work1(); work2(); work3(); work4(); }