一、命名空间
命名空间的理解:
命名空间相当于把原先的全局范围进一步分割成更多的“全局“命名空间。如果要使用命名空间里定义的,必须使用using声明访问这个命名空间,不然该命名空间不可访问。
命名空间的使用:
命名空间可以在需要的地方任意标注(随时追加),相当于把原先属于全局的范围圈起来,标上属主。命名空间在头文件里定义,也跟原先的头文件使用一样,只能声明,不能定义变量。
namespace n1
{
int a = 3;
};
int main() {
cout << a << endl;
return 0;
}
/usercode/file.cpp: In function ‘int main()’:
/usercode/file.cpp:10:14: error: ‘a’ was not declared in this scope
cout << a << endl;
^
/usercode/file.cpp:10:14: note: suggested alternative:
/usercode/file.cpp:6:9: note: ‘n1::a’
int a = 3;
1、命名空间的函数可以访问全局变量/函数,但如果命名空间里有"全局"变量/函数,优先使用该命名空间里的,找不到的话再到外面去找namespace n1
namespace n1
{
int a = 3;
void test3n();
class c1
{
public:
static void test1();
};
}
int a = 33;
int b = 45;
void test3g()
{
cout << "3g" << endl;
}
void n1::test3n()
{
cout << "3n" << endl;
}
void n1::c1::test1()
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
test3n();
test3g();
}
int main() {
n1::c1::test1();
return 0;
}
/***************/
a = 3
b = 45
3n
3g
2. 命名空间起别名
namespace bieming = n1;
cout << bieming::a << endl;
3. 匿名命名空间
匿名命名空间相当于在函数或变量前加static,限制只能在本文件使用。
namespace
{
int a = 3;
namespace n2
{
int b = 13;
}
}
int main() {
cout << a << endl;
cout << n2::b << endl;
return 0;
}
4. using声明
(1) using声明使用了某个变量,这个函数体里不能再定义同名的变量,不然不知道用哪个了namespace n1
namespace n1
{
int b = 13;
}
int main() {
float b;
using n1::b;
cout << b << endl;
return 0;
}
/tmp/368202763/main.cpp:12:12: error: target of using declaration conflicts with declaration already in scope
using n1::b; ^
/tmp/368202763/main.cpp:7:6: note: target of using declaration
int b = 13; ^
/tmp/368202763/main.cpp:11:8: note: conflicting declaration
float b;
(2) using声明使用某个函数,包含了这个函数的所有重载版本
(3) 函数体内使用using声明使用某命名空间里的函数/变量, 如果函数体外有全局的同名函数或变量,要使用外部的,加::
namespace n1
{
int b = 13;
void test() { cout << "n1" << endl; }
}
void test() { cout << "global test" << endl; }
int b = 22;
int main() {
using n1::b;
using n1::test;
cout << b << endl;
test();
cout << ::b << endl;
::test();
return 0;
}
/*************/
13
n1
22
global test
5. using编译指令
格式:using namespace n1;
using编译指令声明使用某个命名空间,那么不可避免的在使用了using编译指令的地方,很可能定义的变量或函数有可能与using命名空间里的同名,所有使用上与using声明不同。
(1) 使用了using编译指令的函数体内如果定义了同名的变量,优先使用函数体内定义的,不会产生二义性。如果函数体内没有,函数体外有全局的与using空间里的同名,要指明使用哪个
(2) 使用了using编译指令的函数体内,如果函数体外的函数与using空间里的同名,也要指定使用哪个namespace n1
namespace n1
{
int b = 13;
void test() { cout << "n1" << endl; }
}
void test() { cout << "global test" << endl; }
int b = 22;
int main() {
int b = 77;
using namespace n1;
cout << b << endl;
cout << ::b << endl;
cout << n1::b << endl;
::test();
n1::test();
return 0;
}
/**********************/
77
22
13
global test
n1
二、 c++对c的增强
1. c中函数的传参,返回值,可以没有类型,c++中必须有
test(a, b)
{
printf("haha\n");
return 33;
}
2. c函数()代表可以不传参,也可以传任何参数。c++中()不能传参
3. c++中类型转换增强
enum
{
ONE_val,
TWO_val
}e1;
int main() {
e1 = ONE_val;
e1 = 1;
return 0;
}
/tmp/270508689/main.cpp:20:7: error: assigning to 'enum (anonymous enum at /tmp/270508689/main.cpp:13:1)' from incompatible type 'int'
e1 = 1;
4. struct结构体增强
c++中struct结构体里可以定义函数,c++中的struct是小型的class,但struct里的成员都是public属性,这是与class的不同。使用struct定义变量,可以省略struct关键字。
struct stru
{
int a;
void test();
static void test2();
}s1;
void stru::test() { cout << "a" << endl; }
void stru::test2() { cout << "b" << endl; }
int main() {
s1.a = 33;
s1.test();
stru::test2();
return 0;
}
5. c++中增加了bool类型
c++中bool类型长度为1个字节。赋值给bool类型变量,可以是true(1), false(0), 也可以是其他数字,如果不是0,就会转为1。bool类型变量的值就是1或者0
bool b1 = true, b2 = -2, b3 = 0, b4 = false;
cout << b1 << endl; //1
cout << b2 << endl; //1
cout << b3 << endl; //0
cout << b4 << endl; //0
6. 三目运算增强
void test() {
int a = 12, b = 8, c = 22;
cout << "a、b中较大的数位: " << (a > b ? a : b) << endl;
b > c ? b : c = 88; //b > c不成立的时候,返回c的引用,并赋值c=88
cout << "c = " << c << endl;
}
注:c、c++中均不支持a < b < c这种操作
c
int a = 3;
if (1 < a < 2)
printf("yes\n");
else
printf("no\n");
//yes
c++
int a = 3;
if (1 < a < 2)
cout << "yes" << endl;
else
cout << "no" << endl;
//yes
三、c++ const关键字
(1) c中const修饰的全局变量,链接属性是外部链接。所以无法在多个文件定义同名的全局变量。而c++中const修饰的全局变量是内部链接、文件作用域,多个文件可以定义同名的const全局变量,其他文件如果要使用该const变量,要声明extern。
a.c
const int g_a = 10;
int main()
{
return 0;
}
b.c
int g_a = 13;
编译报错
lipenghui@lipenghui-virtual-machine:/mnt/hgfs/vmshare/c++$ gcc -o test a.c b.c
/tmp/ccWTOt7C.o:(.data+0x0): `g_a’被多次定义
/tmp/ccOtOFnh.o:(.rodata+0x0):第一次在此定义
collect2: error: ld returned 1 exit status
/*********************** cpp *******************/
a.cpp
const int g_a = 30;
extern int g_b;
int main()
{
cout << "g_a = " << g_a << endl;
cout << "g_b = " << g_b << endl;
return 0;
}
b.cpp
const int g_a = 13;
extern const int g_b = 88;
root@lipenghui-virtual-machine:/mnt/hgfs/vmshare/c++# g++ -o test a.cpp b.cpp
root@lipenghui-virtual-machine:/mnt/hgfs/vmshare/c++# ./test
g_a = 30
g_b = 88
(2) c中const全局变量存放在只读数据段,无法修改,无论是直接修改,还是通过指针的方式,都会报段错误。const局部变量放在栈上,无法直接修改,但可以通过指针的方式对其修改。
(3) c++中const全局变量同c中const全局变量一样无法修改,无论是直接修改,还是通过指针的方式,都会报段错误。
(4) c++中const局部一般是不可以修改的,通过指针修改,不会报段错误,但修改的是临时分配内存的变量,而原先的变量根本动不了。但也有可以修改的情况。用普通变量初始化const局部变量,分配内存,可以通过指针的方式修改变量值。自定义类型的const变量,也可以通过指针的方式修改。
//普通情况下,不分配内存,放在符号表。取地址时,分配临时内存。改不了原先的变量
void test02()
{
int a = 3;
const int b = a;
int *p = (int*)&b;
*p = 100;
cout << "b = " << b << endl;
}
const修饰的自定义数据类型变量,分配内存,可以通过指针的方式修改变量值
struct person
{
string name;
int age;
};
void test02()
{
const person p1;
//p1.name = "xiao ming"; //无法直接修改,可以通过指针改
person*p = (person*)&p1;
p->age = 33;
p->name = "xiao hong";
cout << "name = " << p1.name << endl;
cout << "age = " << p1.age << endl;
}
(5) c中const总是要分配内存的,c++中一般不分配,但根据使用情况有时也会分配
c中const全局变量分配在只读数据段,局部const变量分配在栈上。
c++中普通const全局变量放在符号表,不分配。声明为extern全局变量时,分配内存,放在只读数据段。普通的const局部变量也会放在符号表,不分配内存。但用变量初始化const局部变量或者自定义的const局部变量,会分配内存,可以修改。
(6) const与define
const有作用域,define宏没有作用域(从声明的地方开始到文件尾,或到undef处).
可以在命名空间中定义宏,但定义的宏不受命名空间的约束,使用时不需要指明命名空间,和在外面全局定义一样。
(1) define作用域从声明到undef或文件尾
void test()
{
#define DA 5
int a = DA;
printf("a = %d\n", a);
}
void test01()
{
int b = DA;
printf("b = %d\n", b);
}
void test02()
{
//#undef DA
int c = DA; //error: ‘DA’ undeclared
printf("c = %d\n", c);
}
(2) define不受命名空间的约束 返回
namespace NM
{
#define DA 32
}
void test02()
{
//int p = NM::DA; //错误
//int DA; //重名错误
cout << "DA = " << DA << endl;
}
四、引用
1. 引用的一般用法
引用就是起别名,是个符号,引用的地址和变量本身是一样的,操作引用与操作原来的变量是一样的,系统并不会为引用分配内存。
引用变量定义的同时必须初始化,int &a = b,之后再使用a = c,编译器会理解为赋值,所以引用初始化之后不能再改变引用的指向。
void test02() {
int a = 5, b = 7;
//int &c; //未初始化,错误
int &c = a;
int &d = a;
cout << "&a = " << &a << " &c = " << &c << " &d = " << &d << endl; //地址相同
c = 55;
cout << "a = " << a << endl; //操作引用和操作变量效果一样
}
2. 数组的引用
方法一:
int a[10];
int (&b)[10] = a;
b[0] = b[1] = 22;
方法二:
typedef int arr[10];
int a[10];
arr &b = a;
b[0] = b[1] = 22;
3. 函数的传参和返回值中使用引用
传参引用,函数中对引用进行的操作,会修改外部引用本身。
返回值为引用,必须像返回一个指针一样对待(引用变量本身的生命周期),引用关联的内存必须存在且合法,所以无法返回局部变量的引用。
如果函数做左值,必须返回引用。
/************** 不要返回局部变量的引用 ******/
int& test01() {
int a = 10;
return a;
}
void test() {
int &a = test01();
}
/*************** 可以返回静态局部变量的引用 *********/
int& test01() {
static int a = 10;
cout << "a = " << a << endl;
return a;
}
void test() {
int &a = test01();
a += 10;
test01();
}
/**************** 如果函数做左值,那么必须返回引用 ****/
int& test01() {
static int a = 3;
cout << "a = " << a << endl;
return a;
}
void test() {
test01() = 20;
test01();
}
4. 指针的引用
c++中使用指针的引用,可以代替使用二级指针,更简单清晰。
int test(int *&p, int &b) {
p = &b;
return 0;
}
int main() {
int a = 3, b = 4;
int *p = &a;
cout << *p << endl; //3
test(p, b);
cout << *p << endl; //4
return 0;
}
5. 常量引用 常引用(const修饰的引用)
常量引用的格式: const Type& ref = val;
常量引用,只能通过引用访问变量,不能通过引用修改。
常量引用的使用一:
字面量(常量)不能赋值给普通引用,但可以赋值给常引用。
int& ref = 100; //错误
//error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
const int& ret = 100; //可以
常引用的最重要用法:
常量引用,常用在函数传参中。普通的引用传参,在函数中可以修改引用,使用了常量引用后,只能访问,不能修改,起到保护作用。
/************** 常量引用,只可以访问,不可以修改 **********/
void test(void) {
int a = 33, b = 88;
int &p1 = a;
//普通引用,可以修改
p1 = b;
printf("p1 = %d\n", p1);
const int &p = a;
//常引用,不能修改
//p = b;
//p = 35; //引用p无法修改
a = 34; //引用指向的变量可以修改
printf("p = %d\n", p);
}
/*************** 函数传参常引用,只能访问,不能修改 *******/
void test01(const char &bb)
{
cout << "bb = " << (int)bb << endl;
//bb = 54; // assignment of read-only reference ‘bb
}
void test()
{
char a = 78;
test01(a);
}
6. 引用的本质
引用的本质在c++内部实现是一个指针常量
Type& ref = val; // Type* const ref = &val;
c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。
void testFunc(int& ref) //发现是引用,转换为 int* const ref = &a;
{
ref = 100; // ref是引用,转换为\*ref = 100
}
int main()
{
int a = 10;
int& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化
aRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;
cout << "a:" << a << endl;
cout << "aRef:" << aRef << endl;
testFunc(a);
return EXIT_SUCCESS;
}
五、内联函数inline
预定义宏像函数,但没有函数的参数、返回值类型检查,使用时原地展开,这些都可能带来一些问题。预定义宏不能定义在类里面作为类的成员函数。c++中内联函数屏蔽了以上缺点。
例1:
#define ADD(x,y) x+y
inline int Add(int x,int y){
return x + y;
}
void test(){
int ret1 = ADD(10, 20) * 10; //希望的结果是300
int ret2 = Add(10, 20) * 10; //希望结果也是300
cout << "ret1:" << ret1 << endl; //210
cout << "ret2:" << ret2 << endl; //300
}
例2:
#define COMPARE(x,y) ((x) < (y) ? (x) : (y))
int Compare(int x,int y){
return x < y ? x : y;
}
void test02(){
int a = 1;
int b = 3;
//cout << "COMPARE(++a, b):" << COMPARE(++a, b) << endl; // 3
cout << "Compare(int x,int y):" << Compare(++a, b) << endl; //2
}
六、函数的默认参数
可以为函数声明设置默认参数,当函数调用时,如果没有传参,使用默认的参数。
函数的参数从左到右,如果一个参数设置了默认参数,那么这个参数右边的所有参数都要设置默认参数。因为如果这个不传参,而后面的需要传参,那么没发写了。
函数声明和函数定义分开写,只能在声明处设置默认参数,不能在定义处设置,因为可能使用者只能看到声明,而定义是自己能看到的。声明处写了默认参数后,定义处不能再写默认参数了。
示例代码
/**************** 声明与定义在一起 *************/
void test01(int a, int b = 10, int c = 20)
{
cout << "sum = " << (a + b + c) << endl;
}
void test()
{
test01(5);
}
/***************** 声明与定义分开,在声明处设置默认参数 ******/
void test01(int a, int b = 10, int c = 20);
void test()
{
test01(5);
}
void test01(int a, int b, int c)
{
cout << "sum = " << (a + b + c) << endl;
}
七、函数的占位参数
函数声明可以设置占位参数,占位参数与普通参数一样可以设置默认参数,只是没有名字只有类型。函数内部一般不能使用占位参数。
示例代码
/************* 占位参数如果没有设置默认值,必须进行传参 ********/
void test01(int) { }
void test()
{
test01(); //error: too few arguments to function ‘void test01(int)’
}
/************* 为占位参数设置默认参数值 *************/
void test01(int a, int b, int = 20);
/************** 函数内部无法使用占位参数 ***********/
void TestFunc01(int a,int b,int) {
cout << "a + b = " << a + b << endl;
}
八、函数重载
(1) 重载条件:同一个作用域 参数个数不同 参数类型不同 参数顺序不同
返回值不作为重载依据,因为我们可以忽略函数的返回值.
函数重载和默认参数一起使用时,要注意二义性问题
示例代码
(2) 函数重载实现原理
编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如void func(); 编译器可能会将函数名修饰成_func,当编译器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char我这里使用”可能”这个字眼是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。
void func(){}
void func(int x){}
void func(int x,char y){}
以上三个函数在linux下生成的编译之后的函数名为:
_Z4funcv //v 代表void,无参数
_Z4funci //i 代表参数为int类型
_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型
九. extern “C”
在Linux下测试:
c函数: void MyFunc(){} ,使用gcc编译,被编译成函数: MyFunc
c++函数: void MyFunc(){},使用gcc编译,被编译成函数: _Z6Myfuncv
通过这个测试,由于c++中需要支持函数重载,所以c和c++中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题,如果在c++中调用一个使用c语言编写模块中的某个函数,会按照c++编译的名称去找,这时当然找不到.所以当调用c文件中的函数时,先声明要调用的c文件中函数加extern “c”,表示调用时按照c的方式去找,而不是c++.
十、 类和对象
1. 类成员的访问权限
在类的内部,没有访问权限之说,所有成员都可访问。访问权限是对外部而言。
不涉及继承和派生的情况,类外只能访问public成员。
class默认访问权限是private(没有说明public private protected的情况下),struct默认访问权限是public。
2. 构造函数和析构函数
(1) 参数
构造函数可以无参,可以有参,可以重载。析构函数不能有参数,不能重载。
构造函数和析构函数都不能有返回值。
(2) 分类
构造函数按有无参数,分为有参构造和无参构造。按类型分为普通构造和拷贝构造。
如果未定义无参构造,而定义了有参构造(包括拷贝构造),那么定义变量时必须进行传参,因为没有对应的无参构造。
拷贝构造:person(const person&p1){}
拷贝构造的传参最好加const,不然在某些情况下会出错。比如person p1 = person(100)。如果拷贝构造没有加const,编译会报错。
(3) 构造函数的调用方式
3.1 括号法
无参:person p1; !!! 不能带括号
有参:person p1(3);
拷贝:preson p2(p1);
3.2 匿名对象
void test() {
person(); //无参匿名对象,没有名字的对象
person(5); //有参匿名对象,没有名字的对象
cout << "ha ha " << endl;
}
匿名对象,首先调用构造函数,然后函数检测到匿名对象没有赋值给一个变量,就紧接着调用析构函数,而不是在这段代码结束之后。
不能调用拷贝构造初始化匿名对象
Person p1;
Person(p1); //错误
显示调用构造函数,将一个匿名对象改名
无参:Person p1 = person(); //打印无参构造
有参:person p2 = person(3); //打印有参构造
拷贝:peson p3 = person(p1); //打印拷贝构造
3.3 等号法,隐式转换
有参:Person p1 = 100; //打印有参构造
拷贝:person p2 = p1; //打印拷贝构造
(4) 拷贝构造函数的调用时机
用一个变量初始化另一个变量时,调用拷贝构造。
Peson p1;
Peson p2(p1);
Peson p3 = person(p1);
Person p4 = p1;
函数传参传普通变量时调用拷贝构造,传引用的话不会调用构造函数
void test01(person p1) //传普通变量,调用拷贝构造
{
cout << "test01" << endl;
}
void test()
{
person p1 = 100;
test01(p1);
}
//传引用的话,不会调用构造函数
void test01(person &p1)
{
cout << "test01" << endl;
}
(5)函数返回局部对象的情况
5.1 返回的局部对象用于初始化外部对象
在Ubuntu下不会再次调用构造函数,且暂时不会析构。外面的变量的地址与局部对象的地址相同,在外面变量结束的时候,才会调用析构函数。对象得到了传递。
class cla
{
public:
cla(const cla &p1) { cout << "kao bei gou zao" << endl; }
cla(int g) { cout << "you can gou zao" << endl; }
cla() { cout << "wu can gou zao" << endl; }
~cla() {
cout << "xi gou han shu" << endl;
}
};
cla test() {
cla c1;
cout << "neibu &c1 = " << (unsigned long)&c1 << endl;
return c1;
}
int main() {
cla c2 = test();
cout << "&c2 = " << (unsigned long)&c2 << endl;
cout << "bye bye" << endl;
return 0;
}
wu can gou zao
neibu &c1 = 140736813384191
&c2 = 140736813384191
bye bye
xi gou han shu
5.2 返回的局部对象用于赋值外部对象:
如果外面接这个函数返回值的对象已经有了(已经调用构造生成了),那么在返回局部对象后这个局部对象就析构了,外面是正常的运算,不会再调用构造函数。
示例代码
int main() {
cla c2;
c2 = test();
cout << "&c2 = " << (unsigned long)&c2 << endl;
cout << "bye bye" << endl;
return 0;
}
wu can gou zao
wu can gou zao
neibu &c1 = 140737282950655
xi gou han shu
&c2 = 140737282950654
bye bye
xi gou han shu
(5) 构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加3个函数
1.默认无参构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝(浅拷贝)
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
3. 构造函数的初始化列表
通过初始化列表,可以设置默认值,可以设置传参。
class person
{
public:
person():age(18),height(180),name(“xiao hong”) { }
person(string m_name):age(20), height(175), name(m_name) { }
person(int m_age, int m_height, string m_name):age(m_age), height(m_height), name(m_name) { }
}
4. 类对象作为类的成员
类对象作为类的成员,可以使用初始化列表的方式,对类成员进行有参构造。
5. explicit关键字
构造函数前面加explicit后,禁止通过隐式类型转换(=法)进行对象的初始化。
class cla
{
public:
explicit cla(int ma):a(ma) {}
int a;
};
int main()
{
cla c1 = cla(10); //这种可以
//cla c2 = 20; //explicit 这种不可以
/usercode/file.cpp: In function ‘int main()’:
/usercode/file.cpp:15:14: error: conversion from ‘int’ to non-scalar type ‘cla’ requested
cla c2 = 20;
}
6. new-delete操作
(1) new操作符在堆上为对象开辟空间,自动调用构造函数,返回对象指针。如果传参,根据传参的类型调用相应的构造函数。
Person *p1 = new person; //调用无参构造 也可以new person()
Person *p2 = new preson(5); //调用有参构造
Person *p3 = new person(*p2); //调用拷贝构造
delete操作符释放new操作申请的堆内存,并自动调用析构函数。
delete先调用析构,再释放内存,delete只适用于new操作返回的指针。
delete p1;
(2) 数组的new和delete
Char *pchr = new char[10];
Int * pint = new int[10];
//创建数组并进行初始化c++11支持
Int *pint = new int[5]{1, 2, 3, 4, 5};
delete []pchr;
delete []pint;
class cla
{
public:
cla() {
cout << "wu can" << endl;
}
~cla() {
cout << "xi gou" << endl;
}
};
int main()
{
cla *c1 = new cla[3]; //这种可以
delete[] c1;
return 0;
}
wu can
wu can
wu can
xi gou
xi gou
xi gou
(3) 当用void* 接受new出来的指针,无法释放申请的内存,不会调用析构函数,会出现释放的问题。
7. 静态成员
在类定义中,在类成员(包含成员函数和成员变量)前加static,成为静态成员。无论这个类创建了多少个对象,静态成员只有一份,属于这个类的所有对象,被这个类的所有对象共享。
静态成员不属于某个对象,在为对象分配内存时,不包括静态成员所占空间。
静态成员可以通过类名或对象名来使用。
(1) 静态成员变量
静态成员变量在类内声明,在类外初始化,必须紧跟着在类外进行初始化(无论是public还是private等其他属性),如果不初始化后面不能使用这个变量,使用的话会报错。静态成员变量有访问权限,可以为public、private、protected。不能在类内或函数内初始化。初始化后,不创建对象也可使用这个静态变量,通过类名。person::a
初始化方法:int person::per_a = 35; //不用加static
使用方法
class cls
{
public:
static int a, b;
//静态成员函数,不能访问普通成员变量,只能访问静态成员变量
static int show_c() {
//d = 33;
return c;
}
//普通成员函数,可以访问静态成员和普通成员
void show_cd() {
d = 33;
c = 22;
cout << "c = " << c << " d = " << d << endl;
}
private:
static int c;
int d;
};
//紧跟着进行初始化
int cls::a = 33;
int cls::c = 18;
void test() {
cout << "a = " << cls::a << endl;
//cout << "b = " << cls::b << endl; //静态成员未初始化,无法使用
//cout << "c = " << cls::c << endl; //静态成员有访问权限,private类外无法直接访问
cout << "c = " << cls::show_c() << endl;
cls c1;
c1.show_cd();
}
(2) 静态成员函数
普通函数可以访问/修改静态变量和非静态变量。静态成员函数只能访问静态变量,因为静态成员函数不属于某个对象,所以无法访问这个对象的其他成员。
静态成员函数可以在对象创建之前被调用,通过类名的方式。
静态成员函数可以在类中定义,也可以在类中声明,在类外紧跟着定义。
静态成员函数有访问权限(public static、private static等)。
静态成员函数中无法使用this指针。
使用方法
class person
{
public:
static int per_a;
static void show_per();
static void set_per(int a, int b) {
//静态成员函数属于全体对象,内部无法使用this指针,this只能在非静态成员函数中使用
//this->per_a = a;
per_a = a; per_b = b;
}
private:
static int per_b;
};
int person::per_a = 99;
int person::per_b = 17;
//成员函数,紧跟着进行定义
void person::show_per() {
cout << " per_a = " << person::per_a << endl;
cout << " per_b = " << person::per_b << endl;
}
(3) const静态成员 const static / static const
如果希望一个静态成员既能被这个类的所有对象共享,又不能更改,可以在static前加const或const static。const静态成员必须在类内定义并初始化,如果定义时未初始化,后面无法使用。
class cls
{
public:
//普通的静态成员变量必须在类外进行初始化
//static int a = 33;
static int a;
//const修饰的静态变量必须在类内定义并初始化,不能在类外初始化
static const int b = 88;
//const修饰的静态变量如果定义时未初始化,不能使用
const static int c;
const static int d = 111;
};
int cls::a = 33;
//int cls::c = 66; //const修饰的不能在类外初始化
void test() {
cls::a = 331;
cout << "a = " << cls::a << endl;
//const修饰的static变量无法修改
//cls::b = 88;
cout << "b = " << cls::b << endl;
//cout << "c = " << cls::c << endl;
cout << "d = " << cls::d << endl;
//cls::d = 222;
}
(4) 类对象实现单例模式
单例模式的目的:让类中只有一个实例
实现方法:
创建一个私有的static person*p_single,
将无参构造私有化,这样初始化p_single的时候,可以调用类内的private 无参构造,类外无法通过无参构造的方式创建对象
将拷贝构造私有化,这样类外无法通过拷贝构造的方式创建对象
如果拷贝构造不私有化,可以通过以下方式创建对象
person p2 = p1
personp2 = new person(*p1)
提供static实例方式,这样类外可以通过类名的方式获取唯一的对象指针。
class printer
{
public:
static printer* get_instanse() {
return p_single;
}
void use_printer(string context) {
cout << "print: " << context << endl;
cout << "yi dayin " << this->times << " ci" << endl;
this->times++;
}
~printer() {
cout << “xi gou diao yong” << endl;
}
private:
printer(){times = 1;}
printer(printer&p1){}
static printer* p_single;
int times;
};
printer* printer::p_single = new printer;
void test()
{
printer *p1 = printer::get_instanse();
p1->use_printer("kai xue le");
p1->use_printer("fang jia le");
delete p1;
}
8. 类成员变量、成员函数的存储
(1) 成员变量、成员函数分开存放,非静态成员变量,包含在类对象中。
函数不包含在类对象中,非内联函数仅有一个实例。
(2) 空类的大小为1。
(3) this指针不占用对象空间,不影响sizeof
/********* 大小为8 对齐 ******/
class CLAS
{
int a;
char dd;
static int b;
void test() { int a = 3;}
};
/************ 大小为1 ************/
class CLAS
{
char dd;
static int b;
};
/************* 大小为1 *********/
class CLAS
{};
9. const修饰成员函数-常函数
const修饰的成员函数,函数体内无法修改普通成员变量,变量前加mutable的可修改(mutalbe int a)。mutable [ˈmjutəbəl] adj:异变的,性情不定的
使用方法 void show_info() const{}
class clas
{
public:
//普通成员函数可以修改成员变量
void change_value(int a) {
this->a = a;
}
//const成员函数只能修改mutable修饰的成员变量
void change_value2() const
{
b = a; //可以访问普通成员变量,但不可以改
//a = 33; //不可以改
b = 34;
}
private:
int a;
mutable int b;
};
10. const修饰对象-常对象
使用方法: const clas c1;
class clas
{
public:
//常对象的类定义,必须有显式构造函数
clas() {}
void change_value() {}
void change_value2() const {}
int a;
mutable int b;
};
void test() {
clas c1;
const clas c2;
c1.change_value();
c1.change_value2();
//常对象只能访问const成员函数,不能访问普通成员函数
//c2.change_value();
c2.change_value2();
//常对象可以访问成员变量,但只能修改mutable修饰的成员变量,普通成员变量不能修改
c1.a = 22;
//c2.a = 33;
c2.b = 55;
}
11. 友元
目的:访问私有成员(成员属性、成员函数) 使用方法
全局友元函数,其他类友元函数,友元类。
友元类内可以访问私有成员,类外不可以。友元类内的所有成员函数都是目的类的友元函数,友元类使用是通过类的函数来访问私有成员,所以友元类与友元成员函数是一样的。
Class person
{
friend void test();
friend class cla;
friend void class::test();
}
(1) 全局函数、其他类的成员函数做友元
//1.友元成员函数中使用了clas,所以必须先声明,在使用之前
class clas;
class fred
{
public:
//2.友元成员函数中访问了clas的成员,所以函数定义必须在clas类定义之后
void changeValue(clas &c1, int val);
};
class clas
{
//声明友元,可以在类成员之前,可以在public下,可以在private下,都可以
friend void test01(clas &cls, int val);
friend void fred::changeValue(clas &c1, int val);
public:
void show_value() {
cout << "a = " << this->a << endl;
}
private:
int a;
void change_value(int ma) {
a = ma;
}
};
//友元成员函数
void fred::changeValue(clas &c1, int val) {
c1.a = val;
c1.show_value();
c1.change_value(41);
c1.show_value();
}
//友元全局函数
void test01(clas &cls, int val) {
cls.a = val; //友元访问私有成员属性
cls.show_value();
cls.change_value(33); //友元访问私有成员函数
cls.show_value();
}
void test02(int val) {
clas c1;
fred f1;
f1.changeValue(c1, val);
}
void test() {
test01(clas(), 10);
test02(321);
}
(2) 其他类做友元
/********************* 例1 ******************/
class clas;
class person
{
public:
void show_value(clas &cl1);
};
class clas
{
friend class person; //友元类
public:
clas(){value = 100;}
private:
int value;
};
void person::show_value(clas &cl1) {
cout << "cl1.value = " << cl1.value << endl; //访问私有数据
}
void test() {
clas c1; person p1;
p1.show_value(c1);
}
/********************* 例2 ******************/
class clas;
class fred
{
public:
fred();
//友元类中可以放目的类的指针,但不能放变量,因为看不到目的类的结构
clas *c1;
//不可以放变量
//clas c2;
void setValue(int val);
void setValue2(int val);
void showValue();
};
class clas
{
friend class fred;
public:
void show_value(){ cout << "a = " << this->a << endl;}
private:
int a;
void change_value(int ma){ a = ma; }
};
fred::fred() {
this->c1 = new clas;
}
void fred::setValue(int val) {
this->c1->a = val;
}
void fred::setValue2(int val) {
this->c1->change_value(val);
}
void fred::showValue() {
cout << "a = " << this->c1->a << endl;
}
void test() {
fred f1;
//f1.c1->a = 55; //友元类外无法访问私有成员
f1.setValue(88); //友元类内可以访问私有成员
f1.c1->show_value();
f1.setValue2(888);
f1.c1->show_value();
}
十一. 运算符重载(operator)
(1) 基本描述
如果想让自定义数据类型进行运算,那么就需要重载运算符。
在成员函数或者全局函数里重写一个运算符的函数,函数名 operator+ () {}
(2) 运算符重载可以重载
运算符重载可以有多个版本(可以重载)。
(3) 一元还是二元:
运算符被定义为全局函数时,对于一元是一个参数,对于二元是两个参数。
运算符定义为成员函数,这时可以使用this指针在函数中,对于一元没有参数,对于二元是一个参数。
(4) 关于为什么有些运算符重载只能为成员函数,参考
为什么有的操作符重载函数只能是成员函数?关于有些运算符只能用成员函数重载
1. +加号运算符重载
返回局部对象到外面:
(1) 在内部函数里,这个局部对象的生成调用无参或有参或拷贝构造
(2) 如果外面接这个函数返回值的对象已经有了(已经调用构造生成了),那么在返回局部对象后这个局部对象就析构了,外面是正常的运算,不会再调用构造函数。
如果外面还没有生成(person p1 = test()),那么=作为初始化语句,局部对象不会调用析构,在外部最后使用完了才会析构。
可以是类成员内部重载,也可以是全局重载。
示例代码
class person
{
public:
person():name(""), age(0) { cout << "wu can gou zao" << endl;}
person(string m_name, int m_age):name(m_name), age(m_age) {
cout << "you can gou zao" << endl;
}
person(const person &pconf) {
this->name = pconf.name; this->age = pconf.age;
cout << "kao bei gou zao" << endl;
}
~person() {
cout << "xi gou, my name is " << this->name << " age is " << this->age << endl;
}
void show_info() {
cout << "my name is " << this->name << " age is " << this->age << endl;
}
//成员函数重载
person operator+(person&p1);
person operator+(string name);
string name;
int age;
};
/******************* 1.成员函数重载 *****************************/
person person::operator+(person&p1) //返回局部对象,外面不会再调用构造函数
{
person tmp; //调用无参构造,返回局部对象给外面,外面不会再调用构造
tmp.age = this->age + p1.age;
tmp.name = this->name + " " + p1.name;
return tmp;
}
person person::operator+(string name) //会调用拷贝构造
{
this->name = name;
return *this;
}
/******************* 2.全局函数重载 *********************/
//全局重载与成员函数重载,传参相同时,只能写一个,不然会报错,匹配多个重载的版本
/*
person operator+(person&p1, person&p2) //传参引用可以少调用一次拷贝构造
{
person tmp;
tmp.age = p1.age + p2.age;
tmp.name = p1.name + p2.name;
return tmp;
}*/
person operator+(person&p1, int age) //传参引用可以少调用一次拷贝构造
{
person tmp = p1;
tmp.age += age;
return tmp;
}
void test()
{
person p1("xiao hong", 18); //you can gou zao
person p2("xiao lan", 6); //you can gou zao
person p4 = p1 + p2; //wu can gou zao
p4.show_info(); //my name is xiao hong xiao lan age is 24
person p5; //wu can gou zao
p5 = p1 + p2; //wu can gou zao
p5.show_info(); //xi gou, my name is xiao hong xiao lan age is 24
//my name is xiao hong xiao lan age is 24
person p3; //wu can gou zao
p3 = p3 + "wu kong"; //kao bei gou zao //xi gou, my name is wu kong age is 0
p3.show_info(); //my name is wu kong age is 0
p3 = p3 + 10; //kao bei gou zao //xi gou, my name is wu kong age is 10
p3.show_info(); //my name is wu kong age is 10
person p6 = p1 + 14; //kao bei gou zao
p6.show_info(); //my name is xiao hong age is 32
/*
xi gou, my name is xiao hong age is 32
xi gou, my name is wu kong age is 10
xi gou, my name is xiao hong xiao lan age is 24
xi gou, my name is xiao hong xiao lan age is 24
xi gou, my name is xiao lan age is 6
xi gou, my name is xiao hong age is 18
*/
}
2. <<左移运算符重载
目的:重载左移运算符结合友元,使用cout可以输出类成员数据。
重点:定义为成员函数左值限定为类本身。所以不能写到成员函数里,因为使用方法是cout<<p;所以必须为全局,且为二元。
重载函数里左边的参数是cout,cout的类型是ostream,传参是cout的引用,返回值也要是引用。
访问类里的私有数据,函数需要声明为友元函数
示例代码
ostream& operator<<(ostream&cout, person&p1) {
cout << "name is " << p1.name << " age is " << p1.age;
return cout;
}
3. ++自增运算符重载
前置++: ++a
后置++: a++
前置++返回引用(c语言中可以++++a),后置++返回临时对象(c语言中不可以a++++),后置++使用占位参数。
既可以全局实现,也可以类内实现。
示例代码
class cla
{
friend cla operator++(cla& c1, int);
friend cla& operator++(cla& c1);
friend ostream& operator<<(ostream& cout, cla &c1);
public:
cla(int arg):a(arg){}
/*********************** 类内实现 ********************
//后置++ a++
cla operator++(int) {
cla tmp = *this;
this->a++;
return tmp;
}
//前置++ ++a
cla& operator++() {
this->a++;
return *this;
}
**************************************************************/
private:
int a;
};
ostream& operator<<(ostream& cout, cla &c1)
{
cout << c1.a;
return cout;
}
//后置++ a++
cla operator++(cla& c1, int) {
cla tmp = c1;
c1.a++;
return tmp;
}
//前置++ ++a
cla& operator++(cla& c1) {
c1.a++;
return c1;
}
void test()
{
cla c1(22), c2(22);
cla c3 = c1++;
cla c4 = ++c2;
cout << c3 << endl; //22
cout << c4 << endl; //23
//cout << c3++ << endl; //直接打印后置++出错
}
4. ->指针运算符重载
智能指针:
(1)使用new运算符实例化对象时,必须手动调用delete,才会释放堆上的内存,并调用析构函数。使用智能指针,托管new运算符得到的对象指针,这样就会自动调用智能指针的析构函数,释放内存。
(2)重载智能指针的指针运算符,使原先操作对象指针转为操作智能指针,智能指针就像对象指针一样使用,为此还需要重载*号。
(3)指针运算符重载函数中应该返回return this->person->,但编译器做了优化,return this->person即可。
示例代码
class PERSON
{
public:
~PERSON()
{ cout << "xi gou han shu diao yong" << endl; }
void show_age()
{ cout << "my age is " << this->age << endl; }
int age;
};
class SMARTPOINT
{
public:
SMARTPOINT(PERSON* p1) {
this->pe = p1;
}
~SMARTPOINT() {
if (this->pe) {
delete this->pe;
this->pe = NULL;
}
cout << "smart pointer xi gou diao yong" << endl;
}
/********** 重载-> ->重载必须是成员函数,不能是全局函数 *********/
PERSON* operator->() {
return this->pe;
}
/********** 重载* 重载*可以是成员函数,也可以是全局函数 *********/
//成员函数重载*
PERSON& operator*() { //必须返回引用,不能返回对象。因为如果返回对象,对象不能做左值,不能赋值。
return *(this->pe);
}
PERSON *pe;
};
//全局函数重载*
PERSON& operator*(SMARTPOINT &s1) {
return *(s1.pe);
}
void test()
{
SMARTPOINT s1 = new PERSON;
s1->age = 28;
s1->show_age();
(*s1).show_age(); //必须带() (*s1)
}
5. =赋值运算符重载
C++为每一个类默认创建的函数有无参构造、拷贝构造、析构函数和=赋值运算符重载函数。
赋值重载函数只是简单的值拷贝,如果类中有分配内存,那么可能会带来一些问题,所以需要我们自己定义赋值运算符重载函数。
重载的=必须是成员函数。
Person p2 = p1; //调用拷贝构造,浅拷贝
Person p2(“”); p2 = p1; //调用重载的赋值运算符
示例代码
(1)默认的=重载使用浅拷贝,有可能带来问题
class PERSON
{
public:
PERSON(char* name) {
//new申请的内存必须比字符串的长度大1,strcpy拷贝长度等于字符串长度加NULL,如果不 //大于1会报错
this->pname = new char[strlen(name) + 1];
strcpy(this->pname, name);
}
~PERSON() {
cout << "xi gou han shu diao yong" << endl;
if (this->pname) {
delete []pname;
pname = NULL;
}
}
void show_name() {
cout << "my name is " << this->pname << endl;
}
char* pname;
};
void test() {
PERSON p1("xiao hong");
PERSON p2 = p1; //调用默认的拷贝构造,浅拷贝,多次释放内存,造成程序崩溃
}
(2)赋值运算符重载
class PERSON
{
public:
PERSON(const char* name) {
this->pname = new char[strlen(name) + 1];
strcpy(this->pname, name);
}
~PERSON() {
cout << "xi gou han shu diao yong" << endl;
if (this->pname) {
delete[] this->pname;
this->pname = NULL;
}
}
PERSON& operator=(const PERSON&p1) //返回PERSON&,为了实现链式操作,a=b=c
{
if (this->pname) {
delete[] this->pname;
this->pname = NULL;
}
this->pname = new char[strlen(p1.pname) + 1];
strcpy(this->pname, p1.pname);
return *this;
}
void show_name() {
cout << "my name is " << this->pname << endl;
}
char* pname;
};
void test()
{
PERSON p1("xiao hong");
PERSON p2(""); //不能PERSON p2 = p1;这种方式会调用默认的拷贝构造,程序崩溃
PERSON p3("");
p3 = p2 = p1; //链式操作
p3.show_name();
p2.show_name();
}
6. []运算符重载
重载的[]必须是成员函数。
数组的封装示例
class ARRAYR
{
public:
ARRAYR();
ARRAYR(ARRAYR&a1);
~ARRAYR();
void push_back(int val);
int get_data(int index);
int get_size();
int& operator[](int index);
private:
int *pdata;
int size;
int capacity;
};
ARRAYR::ARRAYR() {
this->capacity = 100;
this->size = 0;
this->pdata = new int[this->capacity];
}
ARRAYR::ARRAYR(ARRAYR&a1) {
this->size = a1.size;
this->capacity = a1.capacity;
this->pdata = new int[this->capacity];
memcpy(this->pdata, a1.pdata, sizeof(int)*(this->capacity));
}
ARRAYR::~ARRAYR() {
if (this->pdata) {
delete[] this->pdata;
this->pdata = NULL;
}
}
int ARRAYR::get_data(int index) {
return pdata[index];
}
int ARRAYR::get_size() {
return this->size;
}
void ARRAYR::push_back(int val) {
this->pdata[this->size] = val;
this->size += 1;
}
int& ARRAYR::operator[](int index) {
return pdata[index];
}
void test()
{
ARRAYR a1;
a1.push_back(11);
a1.push_back(21);
a1.push_back(32);
ARRAYR a2(a1);
for (int i = 0; i < a2.get_size(); i++) {
cout << "a2[" << i << "] = " << a2.get_data(i) << endl;
}
a1[2] = 88;
cout << "a1[2] = " << a1[2] << endl;
}
7. ==关系运算符重载
示例代码
class STUDENT
{
public:
STUDENT(int chinese, int math) {
this->chinese = chinese;
this->math = math;
}
//成员函数重载==
bool operator==(STUDENT& s1) {
if ((this->chinese + this->math) == (s1.chinese + s1.math))
return true;
else
return false;
}
private:
int chinese;
int math;
};
//全局函数重载==
bool operator==(STUDENT& s0, STUDENT& s1) {
if ((s0.chinese + s0.math) == (s1.chinese + s1.math))
return true;
else
return false;
}
void test()
{
STUDENT s1(60, 80);
STUDENT s2(70, 70);
if(s1 == s2)
cout << "s1 == s2" << endl;
else
cout << "s1 != s2" << endl;
}
8. ()函数调用运算符重载-仿函数
函数调用重载:类对象() 看上去像函数,仿函数
(1)通过函数对象调用 person p1; p1();
(2)通过匿名对象 person()();
示例代码
例1:
class CLAS
{
public:
void operator()() {
cout << "nihao" << endl;
}
};
void test() {
CLAS c1;
c1();
CLAS()();
}
例2:
class MYADD
{
public:
int operator()(int a, int b) {
return a + b;
}
};
void test() {
int sum, sum2;
MYADD m1;
sum = m1(3, 5);
sum2 = MYADD()(4, 5);
cout << "sum = " << sum << " sum2 = " << sum2 << endl;
}
#
9. 运算符重载总结
= [] ()和 -> 操作符只能通过成员函数进行重载 ,如果定义为全局重载,编译时会报错(operator=(operator[] () ->)必须为成员函数)
<< 和 >>只能通过全局函数配合友元函数进行重载
不要重载 && 和 || 操作符,因为无法实现短路规则
续:关于有些运算符只能用成员函数重载
class cla
{
public:
cla() {
ma = 0;
cout << "wu can gou zao" << endl;
}
cla(int a) {
ma = a;
cout << "you can gou zao, a = " << a << endl;
}
cla(cla &a1) {
ma = a1.ma;
cout << "kao bei gou zao, a = " << a1.ma << endl;
}
~cla() {
cout << "xi gou han shu" << endl;
}
private:
int ma;
};
int main() {
cla a1;
a1 = 5;
cout << "bye bye" << endl;
return 0;
}
wu can gou zao
you can gou zao, a = 5
xi gou han shu
bye bye
xi gou han shu
int main() {
cla a1, a2(10);
a1 = a2;
cout << "bye bye" << endl;
return 0;
}
wu can gou zao
you can gou zao, a = 10
bye bye
xi gou han shu
xi gou han shu
class cla
{
public:
cla() {
ma = 0;
cout << "wu can gou zao" << endl;
}
cla(cla &a1) {
ma = a1.ma;
cout << "kao bei gou zao, a = " << a1.ma << endl;
}
~cla() {
cout << "xi gou han shu" << endl;
}
private:
int ma;
};
int main() {
cla a1;
a1 = 5;
cout << "bye bye" << endl;
return 0;
}
/usercode/file.cpp: In function ‘int main()’:
/usercode/file.cpp:24:8: error: no match for ‘operator=’ (operand types are ‘cla’ and ‘int’)
a1 = 5;
class cla
{
public:
cla() {
ma = 0;
cout << "wu can gou zao" << endl;
}
~cla() {
cout << "xi gou han shu" << endl;
}
cla& operator=(int a) {
ma = a;
cout << "chong zai =" << endl;
return *this;
}
private:
int ma;
};
int main() {
cla a1;
a1 = 5;
cout << "bye bye" << endl;
return 0;
}
wu can gou zao
chong zai =
bye bye
xi gou han shu