Cpp语言基础
部分内容复习
二级指针的作用
- 指向指针的指针的作用:当要修改实参指针的指向的时候,形参必须使用指向指针的指针。
- 当修改的是实参指针指向的内容时,则形参只需是指针即可
1 | void GetMemory(char *p,int num) |
结构是编译能通过,却不能运行,为什么呢?
先说一下指针作为函数参数的意义:当将指针作为参数时,实参向形参传递的是地址,在函数执行过程中,既可以对该参数指针进行处理,也可以对该参数指针所指向的数据进行处理,(以上程序段来说就是可以对p或p进行处理)。*由于此时形参和实参都是指向同一个存储单元,因此当形参指针所指向的数据改变时,实参指针所指向的数据也作相应的改变,因此这时的形参可以作为输出参数使用。(str和p应同时更改!)
按照上面的说法,这个程序应该没有问题的啊,实参str和形参p指向同一个存储单元,给形参分配的内存单元应该也给实参分配了才对啊,问题就是在这里
实参和形参是指向同一个地址,它们只是指向相同,但它们自身的地址不是同时申请的,就是说p在申请内存时,相当于是把p的指向给改了,但是str的指向仍然没有改!所以尽管str调用了GetMemory,但它仍然是个空指针,所以进行strcpy是就不能运行
要使程序可以运行,只要小小的改动就行了(用指向指针的指针):1
2
3
4
5
6
7
8
9
10
11void GetMemory(char **p,int num)
{
*p=(char*)malloc(sizeof(char)*num); //此时*p就变成了是形参本身的地址
}
void main()
{
char *str=NULL;
GetMemory(&str,100);//&str是实参的地址,所以实参和形参之间就可以直接调用
strcpy(str,"hello");
free(str);
}
C++对C语言的加强
namespace命名空间
C++命名空间基本常识
所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h
- 当使用
时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现; - 当使用
的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。 - 在程序开头使用
using namespace std;
,即可使命名空间std内定义的所有标识符都有效
与C的区别
- C中的命名空间
- 在C语言中只有一个全局作用域
- C语言中所有的全局标识符共享同一个作用域
- 标识符之间可能发生冲突
- C++中的命名空间
- 命名空间将全局作用域分成不同的部分
- 不同命名空间中的标识符可以同名而不会发生冲突
- 命名空间可以相互嵌套
- 全局作用域也叫默认命名空间
三目运算符
C语言返回变量的值 C++语言是返回变量本身C语言中的三目运算符返回的是变量值,不能作为左值使用C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方
1
2
3
4
5
6
7
8
9
10int main(void)
{
int a = 10;
int b = 20;
//返回⼀一个最⼩小数 并且给最⼩小数赋值成30
//三⺫⽬目运算符是⼀一个表达式 ,表达式不可能做左值
(a < b ? a : b ) = 30;//在Cpp中是可行的,而C中是错误的
printf("a = %d, b = %d\n", a, b);
return 0;
}三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
(a < b ? 1 : b )= 30;
const增强
C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了1
2
3const int a = 10;
int *p = (int*)&a;
*p = 11;//即可修改a中的值
C++中的const常量类似于宏定义const int c =5; 约等于 #define 5
C++中的const常量与宏定义不同
const常量是由编译器处理的,提供类型检查和作用域检查宏定义由预处理器处理,单纯的文本替换
枚举
c 语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化
1 |
|
引用
变量名,本身是一段内存的引用,即别名(alias)。引用可以看作一个已定义变量的别名1
2
3
4
5
6
7
8
9
10int a = 10; //c编译器分配4个字节内存, a内存空间的别名
int &b = a; //b就是a的别名
a = 11; //直接赋值
{
int *p = &a;
*p = 12;
cout << a <<endl;
}
b = 14;
cout << "a = " <<a << ", b = " << b <<endl;
- 引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址
- &符号前有数据类型时,是引用。其它皆为取地址
- 声明的时候必须初始化,一经声明,不可变更
- 可对引用,再次引用。多次引用的结果,是某一变量具有多个别名
1 | int a,b; |
引用的意义
- 引用作为其它变量的别名而存在,因此在一些场合可以代替指针
- 引用相对于指针来说具有更好的可读性和实用性
1 | void swap(int a, int b); //无法实现两数据的交换 |
引用的本质
1 | int main() |
可见得Cpp编译器在定义引用时是将两个变量名指向同一个内存地址:引用在C++中的内部实现是一个常指针
Type& name <===> Type* const name
C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同
- 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏。
间接赋值的三个必要条件
- 定义两个变量 (一个实参一个形参)
- 建立关联 实参取地址传给形参
- *p形参去间接的修改实参的值
引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。
引用作为函数的返回值(引用当左值)
- 当函数返回值为引用时,若返回栈变量:不能成为其它引用的初始值(不能作为右值使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int getA1()
{
int a;
a = 10;
return a;
}
int& getA2()
{
int a;
a = 10;
return a;
}
//将⼀一个引⽤用赋给另⼀一个引⽤用作为初始值,由于是栈的引⽤用,内存⾮非法
int &a3 = getA2(); 当函数返回值为引用时,若返回静态变量或全局变量可以成为其他引用的初始值(可作为右值使用,也可作为左值使用)
1
2
3
4
5
6
7
8
9int& getA2()
{
static int a;
a = 10;
return a;
}
//将⼀一个引⽤用赋给另⼀一个引⽤用作为初始值,由于是静态区域,内存合法
int &a3 = getA2();引用作为函数返回值,如果返回值为引用可以当左值,如果返回值为普通变量不可以当左值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//函数当左值
//返回变量的值
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;
//函数当左值
//func1() = 100; //error
func2() = 100; //函数返回值是一个引用,并且当左值
c2 = func2();
cout << "c2 = " << c2 <<endl;
return 0;
}
指针引用
1 | struct Teacher |
const 引用
const引用,它可以防止对象的值被随意修改。具有一些特性。
const对象的引用必须是const的,将普通引用绑定到const对象是不合法的
1
2const int a = 1;
int &b = a;//这是不合法的const引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化。这个是const引用与普通引用最大的区别。
1
2
3
4//以下语句是合法的
const int &a = 2;
double x =3.1;
const int &b =a;
1 | //普通引用 |
const 引用的原理
const引用是指向const对象的引用:
通过ref2对ival赋值会导致修改const对象的值,为防止这样的修改,需要规定将普通的引用绑定到const对象是不合法的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
const int ival =1024;
const int &refVal = ival; //两者均为const对象
int &ref2 = ival; //error!不能使用非const引用指向const变量
}
{
double val =3.14;
// double *ref = &val;
// const double& ref = val;
const int& ref = val;
// int& refi = vali; //error
double & ref2 = val;
cout << ref << " " << ref2 <<endl;
val = 5.22;
cout << ref << " " << ref2 <<endl;
}
同样的初始化对于非const引用却是不合法的,而且会导致编译时错误,观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。对于以下代码1
2
3
4
5
6double dval = 3.14;
const int &ri = dval;
//编译器会将这些代码转换为以下形式
int temp = dval;
const int &ri = temp;
可以发现对于将引用绑定到不同类型时,编译器会创建一个int型的暂时变量存储dval,然后将ri绑定到temp上
(Tips:引用在内部存放的是一个对象的地址,它是该对象的别名。对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,引用实际上指向该对象,但用户不能访问它。)
- 结论
const int & e
相当于const int * const e
- 普通引用 相当于
int *const e
- 当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
- 使用字面量对const引用初始化后,将生成一个只读变量
inline内联函数
类似于C语言中宏函数,但是C中的宏函数处理发生在预处理阶段,没有语法检测。
1 | inline void func(int a) |
特点:
- 内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。
- C++编译器直接将函数体插入在函数调用的地方。
- 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)。
- 内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)。
- 内联函数由编译器处理,直接将编译后的函数体插入调用的地方,宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程。
- C++的内联编译应该不包含循环、过多的条件判断、不宜实现太过复杂的的功能。
- 内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。
总结:
- 本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
- 适用场景:函数体很“小”,且被“频繁”调用
函数默认参数
单个默认参数
对于多次调用一函数同一实参时,C++给出了更简单的处理办法。给形参以默认值,这样就不用从实参那里取值了。
1 | //1 若你填写参数,使⽤用你填写的,不填写默认 |
多个默认参数
1 | //2 在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数 |
占位参数
1 | /* |
函数重载
规则(类似Java)
- 函数名相同。
- 参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
- 返回值类型不同则不可以构成重载。
- 一个函数,不能既作重载,又作默认参数的函数。
1 | int func(int a, int b, int c = 0) |