目录
13.1如何恰当的使用public,protected和private为成员声明访问级别?
1. C++对C语言的加强
1.1 namespace命名空间
在C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。为了避免在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入了关键字namespace(命名空间),可以更好地控制标识符的作用域。所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。std是c++标准命名空间,c++标准程序库中的所有标识符都被定义在std中,比如标准库中的类iostream、vector等都定义在该命名空间中,使用时要加上using声明(using namespace std) 或using指示(如std::string、std::vector<int>).
由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
1)直接指定标识符。例如std::ostream。完整语句如下:
std::cout << std::hex << 3.4 << std::endl;
2)使用using关键字。
using std::cout;
using std::endl;
以上程序可以写成 :
cout << std::hex << 3.4 << endl;
3)最方便的就是使用using namespace std; 例如:using namespace std;这样命名空间std内定义的所有标识符都有效。就好像它们被声明为全局变量一样。
C中的命名空间:
在C语言中只有一个全局作用域;
C语言中所有的全局标识符共享同一个作用域;
标识符之间可能发生冲突。
C++中的命名空间:
命名空间将全局作用域分成不同的部分;
不同命名空间中的标识符可以同名而不会发生冲突;
命名空间可以相互嵌套;
全局作用域也叫默认命名空间。
编程训练:
#include <stdio.h>
namespace NameSpaceA
{
int a = 0;
}
namespace NameSpaceB
{
int a = 1;
namespace NameSpaceC
{
struct teacher
{
char name[10];
int age;
};
}
}
int main(void)
{
using namespace NameSpaceA;//方法3
using NameSpaceB::NameSpaceC::teacher;//方法2或者方法3using namespace NameSpaceB::NameSpaceC;
//using namespace NameSpaceB::NameSpaceC;
teacher t1 = {"Tina", 33};
printf("a = %d\n", a); //0
printf("a = %d\n", NameSpaceB::a);//1,方法1
printf("t1.name = %s\n", t1.name); //Tina
printf("t1.age = %d\n", t1.age); //33
return 0;
}
1.2 “实⽤用性”增强
C语⾔言中的变量都必须在作⽤用域开始的位置定义;
C++中更强调语⾔言的“实⽤用性”,所有的变量都可以在需要使⽤用时再定义。
#include <iostream>
using namespace std;
int main(void)
{
int i = 0;
cout << "i = " <<i <<endl;
int k;
k = 4;
cout << "k = " <<k <<endl;
return 0;
}
1.3 struct 类型增强
C语⾔言的struct定义了⼀一组变量的集合,C编译器并不认为这是⼀一种新的类型 ;
C++中的struct是⼀一个新类型的定义声明 。
#include <iostream>
struct Student
{
char name[100];
int age;
};
int main(int argc, char *argv[])
{
Student s1 = {"Tina", 12}; //可以不加关键字struct,但在C语言中一定要加
Student s2 = {"Tom", 21};
return 0;
}
1.4 三⽬目运算符功能增强
1)C语言返回变量的值, C++语言是返回变量本身
C语言中的三目运算符返回的是变量值,不能作为左值使用
C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方。
2)注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用:(a < b ? 1 : b )= 30;
#include <iostream>
using namespace std;
int main(void)
{
int a = 10;
int b = 20;
//返回⼀一个最⼩小数 并且给最⼩小数赋值成30
//三⺫⽬目运算符是⼀一个表达式 ,表达式不可能做左值
(a < b ? a : b ) = 30;
printf("a = %d, b = %d\n", a, b); //输出结果为30,20
return 0;
}
1.5 const 增强
//第一个第二个意思一样 代表一个常整形数
const int a;
int const b;
//第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本⾝身可以修改),常在函数声明时修饰参数的作用
const int *c;
//第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
int * const d;
//第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
const int * const e ;
const在函数声明时修饰参数:
void *memmove( void* dest, const void* src, size_t count ); 这是标准库中的一个函数,在头文件#include <string.h>中声明,其功能为由src所指内存区域复制count个字节到dest所指内存区域。用于按字节方式复制字符串(内存)。它的第一个参数,是将字符串复制到哪里去(dst),是目的地,这段内存区域必须是可写。它的第二个参数,是要将什么样的字符串复制出去,我们对这段内存区域只做读取,不写。于是,我们站在这个函数自己的角度来看,src 这个指针,它所指向的内存内所存储的数据在整个函数执行的过程中是不变。于是src所指向的内容是常量。于是就需要用const修饰。
C语言中const变量是只读变量,有自己的存储空间(通过指针可以被修改)
#include <stdio.h>
int main(void)
{
const int a=10;
printf("%d\n",a);
int *p = (int*)&a;
*p=11;
printf("%d\n",a);//输出11,a的值被改变。C语言中const变量是只读变量,有自己的存储空间
return 0;
}
C++中的const常量(不能被修改)
#include <iostream>
using namespace std;
int main(void)
{
const int a=10;
printf("%d\n",a);
int *p = (int*)&a;
*p=11;
printf("%d\n",a);//输出为10
printf("%d\n",*p);//输出为11
return 0;
}
上述代码中的const int a=10并没有像C语言中那样为a在栈中开辟存储区域,而是在常量区的符号表中增加了一个key与value,key即a,value即10。在这里,a并没有所谓的空间与地址,类似于宏定义。但宏定义是在预处理阶段展开,而后者是在编译阶段。当使用&操作符,取const常量的地址时,会分配临时存储空间,上述代码中指针p即存放该临时空间的地址,修改也只是修改该临时空间的值,a值并不改变。
C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了。故下面代码是合法的,而在C语言中就不合法。在C中a被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时长度必须是“常量”,在栈中分配相应固定大小的存储空间。
const int a =10;
int array[a] = {0};
1.6 枚举的增强
c 语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而 c++中枚举变量, 只能用被枚举出来的元素初始化。
2. 引用
变量名,本身是一段内存的引用。 引用可以看作一个已定义变量的别名。 引用的语法:Type& name = var;
2.1 规则
1 引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
2 声明的时候必须初始化,一经声明,不可变更。
3 可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
4 &符号前有数据类型时,是引用。其它皆为取地址。
#include<iostream>
using namespace std;
int main(void)
{
int a,b;
int &r = a;
int &r = b; //错误,不可更改原有的引⽤用关系
float &rr = b; //错误,引⽤用类型不匹配
cout<<&a<<&r<<endl; //变量与引⽤用具有相>同的地址。
int &ra = r; //可对引⽤用更次引⽤用,表⽰示 a 变量有两个别名,分别是 r 和 ra
return 0;
}
2.2 引用作为函数参数
普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。
#include <iostream>
using namespace std;
struct Teacher
{
char name[64];
int age ;
};
void printfT(Teacher *pT)
{
pT->age = 32;
cout<< pT->age <<endl;
}
void printfT2(Teacher &pT) //当传入参数t1,pT是t1的别名,相当于修改了t1
{
pT.age = 33;
cout<<pT.age<<endl;
}
//pT和t1的是两个不同的变量
void printfT3(Teacher pT)//值拷贝,当参数传入后相当于拷贝整个结构体
{
cout<<pT.age<<endl;
pT.age = 45; //只会修改pT变量 ,不会修改t1变量
}
int main(void)
{
Teacher t1;
t1.age = 35;
printfT(&t1); //32
printfT2(t1); //pT是t1的别名 输出33
printf("t1.age:%d \n", t1.age); //33
printfT3(t1) ;// pT是形参 ,t1 copy⼀一份数据 给pT
printf("t1.age:%d \n", t1.age); //33
return 0;
}
2.3 引用的意义
引用作为其它变量的别名而存在,因此在一些场合可以代替指针。c++中引入引用后,可以用引用解决的问题。避免用指针来解决。
void swap(int a, int b); //⽆无法实现两数据的交换
void swap(int *p, int *q); //开辟了两个指针空间实现交换
void swap(int &a, int &b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
2.4 引用的本质
#include <iostream>
using namespace std;
struct Taacher1
{
int &age;
};
struct Taacher2
{
int *age;
};
int main(void)
{
cout<< sizeof(struct Taacher1)<<" ,"<<sizeof(struct Taacher1)<<endl;//输出8,8
return 0;
}
1)引用在C++中的内部实现是一个常指针 ,初始化只够都不能再被修改
Type& name <===> Type* const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏。
2.5 引用作为函数的返回值
当函数返回值为引用时:
①若返回栈变量:不能成为其它引用的初始值(也不能作为左值使用);
②若返回静态变量或全局变量:可以成为其他引用的初始值(可作为右值使用,也可作为左值使用);
include <iostream>
using namespace std;
int getA1()
{
int a;
a = 10;
return a;
}
int& getA2()
{
int a;
a = 10;
return a;
}
int& getA3()
{
static int a;
a = 10;
return a;
}
int main(void)
{
int a1 = 0;
int a2 = 0;
//值拷⻉贝
a1 = getA1();
//将⼀一个引⽤用赋给⼀一个变量,会有拷⻉动作
//理解: 编译器类似做了如下隐藏操作,a2 = *(getA2())
a2 = getA2();
//将⼀一个引⽤用赋给另⼀一个引⽤用作为初始值,由于是栈的引⽤用,内存非法
int &a3 = getA2();
//将⼀一个引⽤用赋给另⼀一个引⽤用作为初始值,由于是静态区域,内存合法
int &a4 = getA3();
cout <<"a1 = " <<a1<<endl;
cout <<"a2 = " <<a2<<endl;
cout <<"a3 = " <<a3<<endl;
return 0;
}
③如果返回值为引用可以当左值,如果返回值为普通变量不可以当左值。
#include <iostream>
using namespace std;
//函数当左值
//返回变量的值
int func1()
{
static int a1 = 10;
return a1;
}
//返回变量本⾝身 ,
int& func2()
{
static int a2 = 10;
return a2;
}
int main(void)
{
//函数当右值
int c1 = func1();
cout << "c1 = " << c1 <<endl;
int c2 = func2(); //函数返回值是⼀一个引⽤用,并且当右值
cout << "c2 = " << c2 <<endl; //输出为10
//函数当左值
//func1() = 100; //error
func2() = 100; //函数返回值是⼀一个引⽤用,并且当左值
c2 = func2();
cout << "c2 = " << c2 <<endl; //输出为100
return 0;
}
2.6指针的引用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct teacher
{
int id;
char name[64];
};
int getMem(struct teacher ** tpp)
{
struct teacher *tp = NULL;
tp = (struct teacher*)malloc(sizeof(struct teacher));
if (tp == NULL)
{
return -1;
}
tp->id = 100;
strcpy(tp->name, "lily");
*tpp = tp;
return 0;
}
void freeTeacher(struct teacher ** tpp)
{
if (tpp == NULL)
{
return;
}
struct teacher *tp = *tpp;
if (tp != NULL)
{
free(tp);
tp = NULL;
}
}
//不用指针,用引用
int getMem2(struct teacher * &tp)
{
tp = (struct teacher *)malloc(sizeof(struct teacher));
if (tp == NULL)
{
return - 1;
}
tp->id = 100;
strcpy(tp->name, "tony");
return 0;
}
void freeTeacher2(struct teacher * &tp)
{
if (tp != NULL)
{
free(tp);
tp = NULL;
}
}
int main(void)
{
struct teacher *tp = NULL;
getMem(&tp);
cout << "id=" << tp->id << ",name=" << tp->name << endl;
freeTeacher(&tp);
getMem2(tp);
cout << "id=" << tp->id << ",name=" << tp->name << endl;
freeTeacher2(tp);
system("pause");
return 0;
}
2.7const引用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//const修饰引用,一般跟const修饰指针的用途一样,都是作为函数参数,保证该参数是输入参数,是只读,
//在函数内部改变不了外部的值
void printB(const int &re)
{
cout << "re = " << re << endl;
}
int main(void)
{
const int a = 10;
//int &re = a;//err
//如果想对一个常量进行引用,必须是一个const引用
const int &re = a;//ok
int b = 20;
printB(b);
const int &re2 = 10;
//编译器临时开辟一个变量temp=10;const int &re3 = temp
system("pause");
return 0;
}
结论:
1)const int & e 相当于 const int * const e
2)普通引用 相当于 int *const e
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值
分配空间,并将引用名作为这段空间的别名
4)使用字面量对const引用初始化后,将生成一个只读变量
3 inline内联函数
c 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销(编译器的压栈入栈)。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。 C++提供了inline 关键字,实现了真正的内嵌。适用场景:函数体很“小”,且被“频繁”调用。
#include <iostream>
using namespace std;
inline void func(int a)
{
a = 20;
cout << a <<endl;
}
int main(void)
{
func(10);
/*
//编译器将内联函数的函数体直接展开
{
a = 20;
cout << a <<endl;
}
*/
return 0;
}
特点:
1)内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。
2)C++编译器直接将函数体插入在函数调用的地方 。
3)内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)。
4)内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)。
5)内联函数由 编译器处理,直接将编译后的函数体插入调用的地方, 宏代码片段 由预处理器处理, 进行简单的文本替换,没有任何编译过程。
6)C++中内联编译的限制:
不能存在任何形式的循环语句 ,不能存在过多的条件判断语句 ,函数体不能过于庞大 ,不能对函数进行取址操作 ,函数内联声明必须在调用语句之前。
7)编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。
4 函数重载
函数重载(Function Overload):用同一个函数名定义不同的函数(函数参数不同),当函数名和不同的参数搭配时函数的含义不同。
4.1重载规则
1,函数名相同。
2,参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
3,返回值类型不同则不可以构成重载。
4.2函数重载与函数指针
当使⽤用重载函数名对函数指针进⾏行赋值时,根据重载规则挑选与函数指针参数列表⼀一致的候选者 ,严格匹配候选者的函数类型与函数指针的函数类型。
#include <iostream>
using namespace std;
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); //