C++记忆恢复集要之一:C++基础
C++基础
1. 经典头
1 |
|
iostream是C++用于管理输入和输出的头文件,包含了在控制台、文件中输入输出的类和函数。 std是C++定义的一个命名空间,cin、cout都在此命名空间中管理,两种输出方式:
1 声明命名空间:using namespace std;
2 域解析符号: std::cout<<“hello world”<<endl;
2. 面向对象编程语言的三要素:
封装:把客观事物封装成抽象的类,而且仅让可信的类或者对象进行操作;将成员函数和成员变量封装在内部,根据需要设置访问权限。通过成员函数管理内部状态。如C语言结构体内不能定义函数,而C++可以。
继承:一个类可以继承另一个类的成员函数和成员变量,复用性大大增加。
多态:也即一个接口,多种方法;程序在运行时才决定调用的函数,是面向对象的核心概念。分为静态多态(函数重载、运算符重载)和动态多态(虚函数、纯虚函数、虚析构函数、纯虚析构函数);
3. 域解析符(::)
除了用于类内解析,还可以用于全局变量的引用操作。
1 | int a=1000; |
4. 命名空间
C++支持用户定义自己的命名空间,防止协作编程时名称混用导致出错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25namespace eden { //第一个命名空间eden
int num=100;
void test(){cout<<"hello eden"<<endl;
}
}
namespace mo { //第二个命名空间mo
int num=99; //允许同名变量、函数
void test(){cout<<"hello mo"<<endl;}
}
void test1(){
cout<<eden::num<<endl; //域解析符调用特定空间的变量、函数
cout<<mo::num<<endl;
eden::test();
mo::test();
}
void test2(){
using eden::test; //声明使用函数
test(); //直接使用
using mo::num;
cout<<num<<endl;
}
int main()
{ test1();
test2();
}
5. 结构体的使用
C语言虽然也有结构体,但是C语言的结构体封装性差,对结构体内成员保护性、保密性不足,只要编译器认为数据类型匹配、语法合格即可执行,如定义一个结构体形参,将一个异名结构体传入函数一样能够编译执行:
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
struct Boy{ //Boy结构体
char colour[32];//定义32字节的char数组
};
struct Girl{ //Girl结构体
char colour[32];
};
void print_boy(struct Boy *boy){ //注意形参是Boy结构体
printf("Boy is %s\n",boy->colour);//实例化的boy的colour会传入%s
}
void print_girl(struct Girl *girl){ //注意形参是Girl结构体
printf("Girl is %s\n",girl->colour);
}
void test1(){
struct Boy boy={"Blue"};
struct Girl girl={"Red"};
print_boy(&boy);
print_girl(&girl);//符合结构体格式,正常执行;
print_boy(&girl); //报兼容性警告,但可以执行,C语言不具备封装成类性,导致类Girl的类任意调用。
}
int main()
{
test1();
return 0;
}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
using namespace std;
struct Boy{
char colour[32]; //colour指代地址,故char数组不支持boy.colour="xxx"的成员初始化方式,要使用strcpy函数
void print_boy(){
printf("Boy is %s\n",colour);//实例化的boy的colour会传入%s
}
};
struct Girl{
char colour[32];
void print_girl(){
printf("Girl is %s\n",colour);
}
};
void test1(){
Boy boy={"Blue"};
Girl girl={"red"};
boy.print_boy();
girl.print_girl();
}
int main()
{
test1();
return 0;
}
6. C++指针
记录了一些常用的指针操作 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using namespace std;
int main(){
int num=10;
int *a=# //指针定义通过取地址符实现
int *b=a; //指针的传递,将a地址给b,a、b是同等效力的指针
cout<<&a<<endl; //指向存着num存储地址的地址
//结果:0x61fe10
cout<<a<<endl; //num变量的存储地址
//结果:0x61fe1c
cout<<*a<<endl; //解num变量地址,即num变量值
//结果:10
cout<<&b<<endl; //b和a都是int*型,与a是等效的,但b的地址是新开辟的
//结果:0x61fe08
cout<<b<<endl;
//结果:0x61fe1c
cout<<*b<<endl;
//结果:10
return 0;
}
7. const关键字
7.1 const修饰全局、局部变量
1 |
|
表明在C++中const修饰的整型变量是输出不变(取决于符号常量表)的,对局部变量而言,可以通过地址修改地址指向的值,但是访问变量名num仍为原值(而在C语言中num可以因此被修改输出的值);对全局变量而言不可以通过地址操作const修饰的变量(编译发生错误);
C++的const是通过符号常量表进行值查询和输出,这有利于提高效率。const的目的是提高效率,优化系统,假如不想利用符号常量表优化,可使用volatile修饰,表明该变量在系统中是易变,在全局、局部变量中修改均生效,可以通过指针修改const的值:
1
const volatile int num=10;
7.2 const修饰指针变量类型()、指针变量
注意const和指针的相对位置,判断不能修改的是指针变量(如int* const
a,称指针常量),还是指针变量类型(如const int* a,称常量指针)
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
29
30
using namespace std;
int main(){
int num=10;
const int *addr1=#//常量指针:addr1地址指向的整型变量的值是不可修改的(值传递可以,指针修改不可)
printf("*addr1=%d\n",*addr1);
printf("addr1=%d\n",addr1);
int num1=100;
num=1;//合法修改,直接修改变量是允许的
addr1=&num1; //合法修改,成为另一个变量的地址指针
// *addr1=100;//非法修改,即不能通过指针修改变量的值
printf("*addr1=%d\n",*addr1);
printf("addr1=%d\n",addr1);
int *const addr2=# //指针常量:const修饰指针变量
printf("*addr2=%d\n",*addr2);
printf("addr2=%d\n",addr2);
int num2=200;
num =100; //合法修改,直接修改变量仍是允许的
*addr2=100; //合法修改,能通过指针修改变量的值
//addr2=&num2; 非法修改
printf("*addr2=%d\n",*addr2);
printf("addr2=%d\n",addr2);
return 0;
}
7.3. const与#define区别
- const有类型,而#define无类型,前者可进行编译器类型检查
- const有作用域,而#define不重视作用域,默认是定义到文件结尾,如果指定作用域宜使用const。
8. 宏函数
1 |
|
结果22
宏函数本质就是宏定义,在预处理阶段自动替换:++a本身直接替换,20变21,判断结束仍为++a,21变22;
9. 内联函数
内联函数是用inline修饰的函数,运行结果实际上和普通函数调用没有区别,只是在编译过程某些地方会以预定义宏的方式展开,而不像普通函数调用带来额外的开销。简短且多次调用的函数编译器会自动将其看成内联函数,复杂冗长的函数即使加上inline,编译器也不会将其看成内联函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
using namespace std;
inline int ComMax(int a,int b){
return a>b?a:b;
}
int main(){
int a=20;
int b=20;
int num=ComMax(++a,b);
cout<<"max="<<num<<endl;
return 0;
}
结果max=21
++a作为内联函数的实参,直接以21带入内联函数里,因此结果为21。
10. 函数的默认参数
在函数的形参部分可以赋予初始值当作默认值,调用时可省略或者修改参数。
1
2
3
4
5
6
7
8
9
10
11
using namespace std;
void Record(string name,int id,int score=61,string school="五道口"){
cout<<name<<'\t'<<id<<'\t'<<score<<'\t'<<school<<endl;
}
int main(){
Record("eden",99);
return 0;
}
11. 函数重载
C语言中函数地址完全取决于函数名,同名函数无法共存;C++函数地址取决于函数名和参数,只要使用不同个数的形参、或不同数据类型的形参、或不同命名空间管理函数,同名函数可以共存。
Attention:C++中函数重载与默认值调用引起的歧义
1 |
|
根据函数重载条件,两个函数可同时存在,但由于三个参数的Sums使用了默认参数,导致函数调用可同时调用这两个函数,引起冲突。
12. 引用
C++继承C指针的应用,还引入了引用机制。引用的作用是为变量地址起一个别名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using namespace std;
int main(){
int a=100;
int &b=a;
cout<<"Address:"<<&a<<'\t'<<&b<<endl;
cout<<"Num:"<<a<<'\t'<<b<<endl;
b=10;
cout<<"Address:"<<&a<<'\t'<<&b<<endl;
cout<<"Num:"<<a<<'\t'<<b<<endl;
return 0;
}
结果:Address:0x61fe14 0x61fe14 Num:100 100 Address:0x61fe14 0x61fe14 Num:10 10
可见引用和原地址的效果是完全等效的。
12.1 引用的特点:
- 指针变量开辟了新空间,用于存储变量的地址,而引用的作用是为变量地址起一个别名,本身并不开辟空间。
- 引用必须初始化,必须是对已有的空间进行赋别名,不能仅声明,而指针变量是允许的。
- 引用只能初始化一次,初始化之后不能作为其他空间别名。
12.2 引用的应用场景:
1. 值对调的几种传参方式
1 |
|
结果:原输出m=1 n=99;
exchange1后:m=1 n=99,对调无效;基于形参的操作并不影响m、n空间,因此m、n值不会更改,通过值传递对调参数必须加以返回值方式实现对调。
exchange2后:m=99 n=1,对调成功;通过获取地址对值进行修改。 exchange3后:m=1 n=99,对调无效;虽然是地址传递,a、b带有m、n地址,但是交换a、b地址不代表交换m、n地址,没有涉及m、n值操作,应该加上对地址赋值;
exchange4后:m=99 n=1,对调成功;引用是别名,对a、b交换操作就是对m、n操作。
2. 函数的引用作为返回值
1 |
|
static int的作用是使变量a长久存在直到程序结束,使用b引用可以避免新变量带来额外空间开销。值得注意的是,int &类函数执行完成后变量a会销毁,如果不使用static会导致执行失败。
13. 动态开辟空间
13.1 兼容C语言的动态空间方法:malloc、calloc
1 |
|
此外还有比较少用的realloc():用于在已有的空间中动态增加空间,假如已有空间后续已无可用空间,则会将原来空间复制到新空间,释放原来空间并返回新地址。
13.2 C++特有方法:关键字new
1 |
|
13.3 关于C++数据类型对应字节数:
32位编译器: char:1
short:2
int/long/float/指针:4
double:8
64位编译器:
long、指针均改为8位。
14. char指针类型赋值问题
1 |
|
char指针类型直接赋值会更新地址值,导致丢失原来开辟的空间,因此应该通过strcpy函数进行赋值,整型变量则不存在该问题。
15. char*指针问题
1. 指针变量浅拷贝的问题
1 | //这是一个非法程序 |
深拷贝解决指针赋值
1 |
|
2.char*与char[]与string:
在C++中,三个都常被用于定义字符串,如 1
2
3char *str="Hello Eden";
char str[]="Hello Eden";
string str="Hello Eden";
而char str[]则是字符数组,str本身代表数组首元素地址,编译器开辟适宜大小,且把常量字符串拷贝到str中,这种情况下则可修改;值得注意的是,有些字符串函数内含了对字符串的修改(如分割函数strtok,见C++第三篇),因此虽然函数参数类型是char *,在定义时却传入char str[],这是因为不能对char *str进行写(分割)操作;
string是一种抽象的类,可以看成是升级版的char*的抽象,解决了内存释放、读写等可能遇到问题,提供更鲁棒的功能,在第二篇文章将介绍简单实现原理。
16. C++类型转换
C++对数据类型有着严格要求,很多禁止数据类型操作实际上和非法的内存操作相关,但C++也提供了多种数据类型转换方式和模板。
显式转换和隐式转换
这是比较常见的类型转换,所谓显式就是手动转换,隐式就是编译器的自动转换。如
1
2
3int a=-10;
unsigned int b=2;
a+b; //有符号数和无符号数相加,编译器会自动将有符号数转换成无符号数再计算,如果需要无符号结果则%u,有符号%d;
静态转换:static_cast
常用于基本数据类型之间的转换(而基本数据类型的指针转换是不允许的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace std;
void test1(){
int a=0x12345678; //32位
char b=a; //char只保留低八位,即0x78,这是隐式调用静态转换
char c=static_cast<char>(a); //显式静态转换s
////char *c=static_cast<char*>(&a); //指针是不允许的
char c=
printf("b=%c\n",b);
printf("c=%c",c);
}
int main()
{
test1();
return 0;
}1
2
3
4
5
6
7//小指针大内存,上行转换,父类指针对子类进行操作没有安全问题
Son s1;
Parent *p1=static_cast<Parent *>(&s1);
//大指针,小内存,下行转换,子类指针对父类进行操作存在安全问题
Parent p1;
Son *s1=static_cast<Son *>(&p1);
动态类型转换:dynamic_cast
1 | //上行转换允许 |
动态类型转换不支持基本数据类型(包括指针)的转换操作
常量转换:const_cast
允许对常量指针、常量引用进行转换,使其转换称非常量指针、非常量引用而可以被修改和操作
。 1
2
3
4
5
6
7
8
9
10
11
12
13
using namespace std;
int main(){
const int a=10;
int *p=(int *)&a;//法一:强类型转换,可能导致未知问题
*p=100; //改a的地址指向值
int *q=const_cast<int*>(&a); //法二:效果与一相同,但操作更安全
cout<<a<<endl; //符号常量表值,仍为10
cout<<*p<<endl; //修改值生效,100;
cout<<*q<<endl; //与p一致,100
return 0;
}
reinterpret_cast:重新解释转换
类似强制类型转换,强大却也是最不安全的转换方法,整型、指针可相互转换等,不详叙。
17. static关键字
C语言很常用的关键字,C++中涉及的静态成员函数、静态成员变量实例请参考第二篇文章,这里仅回顾其作用特性和提前概述。
static关键字作用:
1.
对象寿命与整个程序一致,但作用域限定为本文件:在一个.c/.h文件定义一个全局静态变量/全局静态函数,只有程序结束其生命周期才结束,但是其他文件也无法引用或者调用这个变量/函数(解除文件耦合,解决命名重复问题)。
内存只有一份拷贝,初始化默认为0(未初始化/初始化为0存于bss段,其余初始化存于.data段,均属于静态变量区),而且只会在程序中初始化一次;注意:如果是静态局部变量,作用域仅在函数体内,且该函数被多次调用,静态局部变量仅会在首次调用初始化一次。
1
2
3
4
5
6
7
8
9
10void count(){
static int num; //静态局部变量:仅第一次调用初始化
num++;
printf("%d ",num);
}
int main(){
count(); //输出1
count(); //输出2
count(); //输出3
}C++内容:修饰静态成员变量:属于类,类对象可共享,遵守访问权限,命名属于类,减少命名空间污染;修饰静态成员函数:属于类,无需实例化对象,只能用访问和操作静态成员函数(因为不含this指针)。const、volatile、virtual关键字都是针对类实例对象,和static使用是冲突、没有意义的。