1.C++对C的加强
1.1 C++命名空间namespace
- 标准C++引入了关键字namespace(命名空间/名字空间/名称空间/名域),可以更好地控制标识符的作用域。
- 当使用的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std ,需要这样做。std::cout。
- C中的命名空间
在C语言中只有一个全局作用域
C语言中所有的全局标识符共享同一个作用域
标识符之间可能发生冲突
- C++中命名空间的概念
命名空间将全局作用域分成不同的部分
不同命名空间中的标识符可以同名而不会发生冲突
命名空间可以相互嵌套
全局作用域也叫默认命名空间
-
C++命名空间的定义: namespace name { … }
-
namespace 的嵌套
'namespace 的定义与嵌套'
namespace namespaceA
{
int a = 10;
}
namespace namespaceB
{
int a = 20;
namespace namespaceC
{
struct Teacher
{
char name[32];
int age ;
};
}
}
void main()
{
using namespace namespaceA;
using namespace namespaceB;
cout<<namespaceA::a<<endl; //a 有歧义所以需要写全是哪个命令空间的a
cout<<namespaceB::a<<endl;
/*显示的, 写全
{
//没用using 所以定义类的对象每次都要指明命名空间
//namespaceB::namespaceC::Teacher t1;
//t1.age = 33;
}
*/
using namespaceB::namespaceC::Teacher ;
Teacher t2;
t2.age = 36;
system("pause");
}
1.2 “实用性”增加
C语言中的变量都必须在作用域开始的位置定义。
C++中所有变量可以在需要使用时再定义
'c语言中报错,C++中可以这样定义变量'
int main11()
{
int i = 0;
printf("ddd");
int k;
system("pause");
return 0;
}
1.3 register关键字增强
register关键字请求“编译器”将局部变量存储于寄存器中,C++中可以取得register变量的地址
1.4变量检测增强
在C语言中,重复定义多个同名的全局变量是合法的
在C++中,不允许定义多个同名的全局变量
C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
int g_var;
int g_var = 1;
C++直接拒绝这种二义性的做法。
1.5 struct类型加强
C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型
C++中的struct是一个新类型的定义声明
struct 关键字 class关键字 完成的功能是一样的
1.6 C++中所有的变量和函数都必须有类型
C++中所有的变量和函数都必须有类型
C语言中的默认类型在C++中是不合法的
'C语言中可执行'
'int f( );表示返回值为int,接受任意参数的函数'
'int f(void);表示返回值为int的无参函数'
'C++中报错'
'int f( );和int f(void)具有相同的意义,都表示返回值为int的无参函数'
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;
}
1.7新增Bool类型关键字
理论上bool只占用一个字节,
如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现
bool类型只有true(非0)和false(0)两个值
bool b2, b3, b4, b5;
//bool变量 为1
b1 = 10;
cout<<"bl:" <<b1<<endl;
//bool变量 为 1
b1 = -10;
cout<<"bl:" <<b1<<endl;
//只有 b1 = 0 布尔值才为0(false)
1.8三目运算符功能增强
C语言返回变量的值 C++语言是返回变量本身
C语言中的三目运算符返回的是变量值,不能作为左值使用
C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方
三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
(a < b ? 1 : b )= 30;
当左值的条件:要有内存空间;C++编译器帮助程序员取了一个地址而已
'c++'
int main()
{
int a = 10;
int b = 20;
(a < b ? a : b )= 30; //三目运算符返回的是变量==》a = 30
printf("a = %d, b = %d\n", a, b);
system("pause");
return 0;
}
'c'
int main()
{
int a = 10;
int b = 20;
//返回一个最小数 并且给最小数赋值成30
//三目运算符是一个表达式 ,表达式不可能做左值
//(a < b ? a : b )= 30; //error
*(a < b ? &a : &b) = 30; //三目表达式返回地址,此地址中存放30
}
2.C / C++ 中的Const
2.1 const 基本概念
const是定义常量==》const意味着只读
//第一个第二个意思一样 代表一个常整形数
//第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
//第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
//第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
int main()
{
const int a;
int const b;
const int *c;
int * const d;
const int * const e ;
return 0;
}
Const好处
1指针做函数参数,可以有效的提高代码可读性,减少bug;
2清楚的分清参数的输入和输出特性
'Const修改形参的时候,在利用形参不能修改指针所向的内存空间'
//指针所指向的内存空间,不能被修改
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.2 const 在C/ C++中
C语言中
const变量是只读变量,有自己的存储空间
C++中
const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了。
可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其它文件中使用,会分配存储空间
当使用&操作符,取const常量的地址时,会分配存储空间
当const int &a = 10; const修饰引用时,也会分配存储空间
'C中可被间接赋值'
int main()
{
const int a = 10;
int *p = (int*)&a;
printf("a===>%d\n", a);
*p = 11;
printf("a===>%d\n", a);
return 0;
}
2.3 const 与define的异同
C++中的const常量类似于宏定义
const int c = 5; ≈ #define c 5
#define, 在编译预处理阶段 处理
const常量是由编译器处理的,提供类型检查和作用域检查
#define d 20
void main()
{
//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]; //c+d 是常量
system("pause");
}
const与#define的区别:作用域
'define可全局调用,const不可以跨函数调用'
void fun1(){
#define a 10
const int b = 20;
//#undef a # undef
}
void fun2(){
printf("a = %d\n", a);
//printf("b = %d\n", b); //error
}
int main(){
fun1();
fun2();
return 0;
}
3.引用
3.1 引用的概念
变量名实质上是一段连续存储空间的别名,程序中通过变量来申请并命名内存空间 , 通过变量的名字可以使用存储空间
引用可以看作一个已定义变量的别名
引用的语法:Type& name = var;
声明的时候必须初始化,一经声明,不可变更
可对引用,再次引用。多次引用的结果,是某一变量具有多个别名
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;
}
引用做函数参数
普通引用在声明时必须用其它的变量进行初始化,
引用作为函数参数声明时不进行初始化
'引用作为函数参数声明时不进行初始化'
void myswap(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void main()
{
int x, y ;
x = 10;
y = 20;
//a就是x的别名 b就是y的别名
myswap(x, y);
printf("x:%d , y:%d \n", x, y);
'普通引用 必须要初始化'
//int &c ; //error
system("pause");
}
复杂类型的引用
'复杂类型的引用'
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);
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
system("pause");
return ;
}
3.2 引用的本质
1.引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2.引用相对于指针来说具有更好的可读性和实用性
3.引用在C++中的内部实现是一个常指针
Type& name <==>Type* const name
4.C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
void func(int &a){
a = 5;
}
void func(int *const a) {
*a = 5;
}
3.3 函数返回值是引用(引用当左值)
当函数返回值为引用时
1.若返回栈变量,不能成为其它引用的初始值,不能作为左值使用
#include <iostream>
using namespace std;
int getA1() {
int a;
a = 10;
return a;
}
int& getA2(){
int a;
a = 10;
return a;
}
int main(void){
int a1 = 0;
int a2 = 0;
//值拷贝
//getA1()的运算结果是一个数值,没有内存地址,不能当左值
//11 = 100;
a1 = getA1();
//将一个引用赋给一个变量,会有拷贝动作
//理解: 编译器类似做了如下隐藏操作,a2 = *(getA2())
a2 = getA2();
//将一个引⽤用赋给另⼀个引⽤用作为初始值,
'由于是栈的引用,内存非法'
int &a3 = getA2();
cout <<"a1 = "<< a1<<endl;
cout <<"a2 = "<< a2<<endl;
cout <<"a3 = "<< a3<<endl;
return 0;
}
2.若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用
#include <iostream>
using namespace std;
int getA1(){
static int a;
a = 10;
return a;
}
int& getA2(){
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();
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;
//函数返回值是一个引用,并且当右值
cout <<"c2 = "<<c2<<endl;
//函数当左值
//func1() = 100; //error
func2() = 100; //函数返回值是一个引用,并且当左值
c2 = func2();
cout <<"c2 = "<<c2<<endl;
return 0;
}
3.4 指针引用
#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");
}
3.5 常引用
1.const 对象的引用必须是 const 的,将普通引用绑定到 const 对象是不合法的。 这个原因比较简单。既然对象是 const 的,表示不能被修改,引用当然也不能修改,必须使用 const 引用
'错误'
const int a = 1;
int &b = a
2.声明const引用
const Type& name = var;
3.可使用变量初始化const 引用,也可使用常量初始化const引用
//const引用让变量(所指内存空间)拥有只读属性
void printTe(const Teacher1 &t)
{
//t.age = 11;
}
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;
}
Teacher1 t1;
t1.age = 33;
return 0;
}
4.const 引用的目的是,禁止通过修改引用值来改变被引用的对象
'当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,'
'并将引用名作为这段空间的别名。使用字面量对const引用初始化后,将生成一个只读变量'
#include <iostream>
using namespace std;
int main(){
double val = 3.14;
const int &ref = val;
double &ref2 = val;
//&ref:0x23fe3c &ref2:0x23fe30 &val:0x23fe30
cout<<"&ref:"<<&ref<<"&ref2:"<<&ref2<<"&val:"<<&val<<endl;
cout << ref<<"__"<<ref2<<endl;
val = 4.14;
cout<<ref<<"__"<<ref2<<endl;
//&ref:0x23fe3c &ref2:0x23fe30 &val:0x23fe30
cout<<"&ref:"<<&ref<<"&ref2:"<<&ref2<<"&val:"<<&val<<endl;
return 0;
}
因为 ref 是 const 的,在初始化的过程中已经给定值,不允许修改。而被引用的对象是 val,是非 const 的,所以 val 的修改并未影响 ref 的值,而 ref2 的值发生了相应的改变。
5.Const & int e 相当于 const int * const e 。 普通引用 相当于 int *const e1
6.当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名。使用字面量对const引用初始化后,将生成一个只读变量
4. C++对C的函数扩展
4.1 inline 内联函数
1.C++编译器可以将一个函数进行内联编译,被C++编译器内联编译的函数叫做内联函数
2.C++编译器直接将函数体插入在函数调用的地方 。
3内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)。
4.内联函数由 编译器处理,直接将编译后的函数体插入调用的地方。宏代码片段 由预处理器处理, 进行简单的文本替换,没有任何编译过程
5.以牺牲代码段空间为代价,提高程序的运行时间的效率。适用于函数体很“小”,且被“频繁”调用
6.C++中内联编译的限制:
不能存在任何形式的循环语句
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
函数内联声明必须在调用语句之前
#define MYFUNC(a, b) ((a) < (b) ? (a) : (b))
//inline int myfunc(int a, int 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
system("pause");
return 0;
}
4.2 默认参数和占位参数
只有参数列表后面部分的参数才可以提供默认参数值
占位参数只有参数类型声明,而没有参数名声明
//默认参数
void myPrint2( int m, int n, int x = 3, int y = 4)
//占位参数
void func1(int a, int b, int)
// func1(1,2); //error
//func1(1,2,3);
//默认参数和占位参数
void func2(int a, int b, int =0)
//func2(1, 2); //0k
//func2(1, 2, 3); //ok
4.3 函数重载
用同一个函数名定义不同的函数
函数重载的判断标准
参数个数不同, 参数类型不同 ,参数顺序不同
返回值类型不同则不可以构成重载
'不是函数重载'
void myPrint(int a, int b)
int myPrint(int a, int b)
'重载与函数默认参数'
void myfunc(int a, int b, int c = 0)
void myfunc(int a, int b)
//myfunc(1, 2); //函数调用时,会产生二义性'