Day 01 C++初识
1.hello world!
#include <iostream> //标准输入输出流
using namespace std;//使用命名空间
int main(){
cout<<"hello world"<<endl;//cout是c++中的标准输出流,endl是输出换行并刷新缓冲区。
return EXIT_SUCCESS;
}
2.面向过程与面向对象
2.1面向过程
面向过程是一种以过程为中心的编程思想。
面向过程编程思想的核心:功能分解,自顶向下,逐层细化(程序=数据结构+算法)。
面向过程编程语言存在的主要缺点是不符合人的思维习惯,而是要用计算机的思维方式去处理问题,而且面向过程编程语言重用性低,维护困难。
2.2面向对象编程
面向对象编程(Object-Oriented Programming)简称 OOP 技术,是开发计算机应用程序的一种新方法、新思想。过使用 OOP 技术,常常要使用许多代码模块,每个模块都只提供特定的功能,它们是彼此独立的,这样就增大了代码重用的几率,更加有利于软件的开发、维护和升级。
在面向对象中,算法与数据结构被看做是一个整体,称作对象,现实世界中任何类的对象都具有一定的属性和操作,也总能用数据结构与算法两者合一地来描述,所以可以用下面的等式来定义对象和程序:
对象 = 算法 + 数据结构 程序 = 对象 + 对象 + ……
从上面的等式可以看出,程序就是许多对象在计算机中相继表现自己,而对象则是一个个程序实体。面向对象编程思想的核心:应对变化,提高复用。
2.3面向对象三大特性
面向对象三大特性:
1.封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数管理内部状态。
2.继承
继承所表达的是类之间相关的关系,这种关系使得对象可以继承另外一类对象的特征和能力。
继承的作用:避免公用代码的重复开发,减少代码和数据冗余。
- 多态
多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
3.C++对C的扩展
3.1作用域运算符
作用域运算符可以用来解决局部变量与全局变量的重名问题,即在局部变量的作用域内,可用::对被屏蔽的同名的全局变量进行访问。
//全局变量
int a = 10;
//1. 局部变量和全局变量同名
void test(){
int a = 20;
//打印局部变量a
cout << "局部变量a:" << a << endl;
//打印全局变量a
cout << "全局变量a:" << ::a << endl;
}
3.2名字控制
c++允许我们对名字的产生和名字的可见性进行控制,在c++中我们将通过一种通过命名空间来控制对名字的访问。
3.2.1 C++命名空间(namespace)
在c++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
3.2.2命名空间使用语法
创建一个命名空间:
namespace A{
int a = 10;
}
namespace B{
int a = 20;
}
void test(){
cout << "A::a : " << A::a << endl;
cout << "B::a : " << B::a << endl;
}
命名空间只能全局范围内定义(以下错误写法)
void test(){
namespace A{
int a = 10;
}
namespace B{
int a = 20;
}
cout << "A::a : " << A::a << endl;
cout << "B::a : " << B::a << endl;
}
命名空间可嵌套命名空间
namespace A{
int a = 10;
namespace B{
int a = 20;
}
}
void test(){
cout << "A::a : " << A::a << endl;
cout << "A::B::a : " << A::B::a << endl;
}
命名空间是开放的,即可以随时把新的成员加入已有的命名空间中
namespace A{
int a = 10;
}
namespace A{
void func(){
cout << "hello namespace!" << endl;
}
}
void test(){
cout << "A::a : " << A::a << endl;
A::func();
}
声明和实现可分离
#pragma once
namespace MySpace{
void func1();
void func2(int param);
}
balabalabala
void MySpace::func1(){
cout << "MySpace::func1" << endl;
}
void MySpace::func2(int param){
cout << "MySpace::func2 : " << param << endl;
}
无名命名空间,意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内部连接
namespace{
int a = 10;
void func(){ cout << "hello namespace" << endl; }
}
void test(){
cout << "a : " << a << endl;
func();
}
命名空间别名
namespace veryLongName{
int a = 10;
void func(){ cout << "hello namespace" << endl; }
}
void test(){
namespace shortName = veryLongName;
cout << "veryLongName::a : " << shortName::a << endl;
veryLongName::func();
shortName::func();
}
3.2.3using声明
using编译指令使整个命名空间标识符可
namespace A{
int paramA = 20;
int paramB = 30;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test01(){
using namespace A;
cout << paramA << endl;
cout << paramB << endl;
funcA();
funcB();
//不会产生二义性
int paramA = 30;
cout << paramA << endl;
}
namespace B{
int paramA = 20;
int paramB = 30;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test02(){
using namespace A;
using namespace B;
//二义性产生,不知道调用A还是B的paramA
//cout << paramA << endl;
cout<<A::paramA<<endl;//可以通过规定空间来确定所需要的值
}
注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。
3.3全局变量检测增强
int a = 10; //赋值,当做定义
int a; //没有赋值,当做声明
int main(){
printf("a:%d\n",a);
return EXIT_SUCCESS;
}
这段代码在c中编译成功,c++中编译失败
3.4 C++中所有的变量和函数都必须有类型
函数检测增强,参数类型增强,返回值检测增强,函数调用参数检测增强
int getRectS(w,h)
{
//return w*h;
}
void twst02(){
getRectS(10,10,10);
}
PS:这里有三处错,int w,int h,返回值,get函数多加一个参量,c语言没报错,c++都报错了
3.5 更严格的类型转换
void test03(){
char *p = (char*)malloc(sizeof(64));
//malloc返回值是*void,虽然万能,但是在c++中会报错,需要强制转换
}
在C++,不同类型的变量一般是不能直接赋值的,需要相应的强转。
3.6 struct类型加强
1.c中定义结构体变量需要加上struct关键字,c++不需要。
2.c中的结构体只能定义成员变量,不能定义成员函数。c++即可以定义成员变量,也可以定义成员函数。
struct Person
{
int m_Age;
void plusAge() { m_Aget+; }; //c++中struct可以加函数
};
void test04(){
Person p1; //使用时候可以不加struct关键字pl.m_Age = 10;
p1.m_Age = 10;
p1.plusAge();
cout << p1.m_4ge <<endl;
}
//1. 结构体中即可以定义成员变量,也可以定义成员函数
struct Student{
string mName;
int mAge;
void setName(string name){ mName = name; }
void setAge(int age){ mAge = age; }
void showStudent(){
cout << "Name:" << mName << " Age:" << mAge << endl;
}
};
//2. c++中定义结构体变量不需要加struct关键字
void test01(){
Student student;
student.setName("John");
student.setAge(20);
student.showStudent();
}
3.7“新增”bool类型关键字
标准c++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。这三个名字都是关键字。
bool类型只有两个值,true(1值),false(0值)
bool类型占1个字节大小
给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)
void test()
{ cout << sizeof(false) << endl; //为1,//bool类型占一个字节大小
bool flag = true; // c语言中没有这种类型
flag = 100; //给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)
}
PS:c语言中的bool类型
c语言中也有bool类型,在c99标准之前是没有bool关键字,c99标准已经有bool类型,包含头文件stdbool.h,就可以使用和c++一样的bool类型。
3.8三目运算符功能增强
c语言三目运算表达式返回值为数据值,为右值,不能赋值。
int a = 10;
int b = 20;
printf("ret:%d\n", a > b ? a : b);
//思考一个问题,(a > b ? a : b) 三目运算表达式返回的是什么?
//(a > b ? a : b) = 100;
//返回的是右值
c++语言三目运算表达式返回值为变量本身(引用),为左值,可以赋值。
int a = 10;
int b = 20;
printf("ret:%d\n", a > b ? a : b);
//思考一个问题,(a > b ? a : b) 三目运算表达式返回的是什么?
cout << "b:" << b << endl;
//返回的是左值,变量的引用
(a > b ? a : b) = 100;//返回的是左值,变量的引用
cout << "b:" << b << endl;
[左值和右值概念]
在c++中可以放在赋值操作符左边的是左值,可以放到赋值操作符右面的是右值。
有些变量即可以当左值,也可以当右值。
左值为Lvalue,L代表Location,表示内存可以寻址,可以赋值。
右值为Rvalue,R代表Read,就是可以知道它的值。
比如:int temp = 10; temp在内存中有地址,10没有,但是可以Read到它的值。
3.9C/C++中的const
3.9.1 const概述
const单词字面意思为常数,不变的。它是c/c++中的一个关键字,是一个限定符,它用来限定一个变量不允许改变,它将一个对象转换成一个常量。
const int a = 10;
A = 100;
//编译错误,const是一个常量,不可修改
3.9.2 C/C++中const的区别
3.9.2.1 C语言中const默认外部链接,C++默认内部链接_
c中const是一个只读变量,给const分配内存。const是一个全局只读变量,其修饰的只读变量是外部连接的。
C++中,const默认内部链接,只有在当前的文件里可以使用,可以使用extern提高作用域,不会分配内存
3.9.2.2const分配内存情况
1.const分配内存 取地址会分配临时内存
void test01()
{
const int a =10;
int*p=(int*)&a;
}
2.加extern提高作用域,编译器也会给const变量分配内存
extern const a=10;
3.用普通变量初始化const的变量
void test02(){
int a =10;
const int b = a;//会分配内存
int *p =(int *)&b;
*p=1000;
cout<<"b="<<b<<endl;
}
4.自定义数据类型
struct Person
{
sttring m_name;
int m_age;
}
void test03(){
const Person p1;
Person *p=(Person*)&p1;
}
3.9.3尽量以const替换#define
const和#define区别总结:
1.const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
2.const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
3.10 引用
3.10.1 引用基本用法
**引用是c++对c的重要扩充。**在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference),它也存在于其他一些编程语言中,并不是c++的发明。
变量名实质上是一段连续内存空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间
对一段连续的内存空间只能取一个别名吗? c++中新增了引用的概念,引用可以作为一个已定义变量的别名。
3.10.1.1基本语法
Type& ref = val;
void test01(){
int a = 10;
//给变量a取一个别名b
int& b = a;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "------------" << endl;
//操作b就相当于操作a本身
b = 100;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "------------" << endl;
//一个变量可以有n个别名
int& c = a;
c = 200;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "c:" << c << endl;
cout << "------------" << endl;
//a,b,c的地址都是相同的
cout << "a:" << &a << endl;
cout << "b:" << &b << endl;
cout << "c:" << &c << endl;
}
//2. 使用引用注意事项
void test02(){
//1) 引用必须初始化
//int& ref; //报错:必须初始化引用
//2) 引用一旦初始化,不能改变引用
int a = 10;
int b = 20;
int& ref = a;
ref = b; //不能改变引用
//3) 不能对数组建立引用
int arr[10];
//int& ref3[10] = arr;
//1. 建立数组引用方法一
typedef int ArrRef[10];
int arr[10];
ArrRef& aRef = arr;
for (int i = 0; i < 10;i ++){
aRef[i] = i+1;
}
for (int i = 0; i < 10;i++){
cout << arr[i] << " ";
}
cout << endl;
//2. 建立数组引用方法二
int(&f)[10] = arr;
for (int i = 0; i < 10; i++){
f[i] = i+10;
}
for (int i = 0; i < 10; i++){
cout << arr[i] << " ";
}
cout << endl;
3.10.2函数中的引用
3.10.2.1通过引用传递参数
void mySwap3(int &a,int &b)//&a = a &b = b
{
int tmp = a;
a = b;
b = tmp ;
}
void test03
{ int a = 10;
int b = 20;
mySwap3(a,b);
cout << "a = " <<a <<end1 ;cout <<"b=" <<b << endl ;
}
通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单:
\1) 函数调用时传递的实参不必加“&”符
\2) 在被调函数中不必在参数前加“*”符
引用作为其它变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。
3.10.2.2引用的注意事项
1.引用必须引一块合法的内存空间
2.不要返回局部变量的应用
//返回局部变量引用
int& TestFun01(){
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& TestFunc02(){
static int a = 20;
cout << "static int a : " << a << endl;
return a;
}
int main(){
//不能返回局部变量的引用
int& ret01 = TestFun01();
//如果函数做左值,那么必须返回引用
TestFunc02();
TestFunc02() = 100;
TestFunc02();
return EXIT_SUCCESS;
}
3.10.3引用的本质
引用的本质在c++内部实现是一个指针常量.
Type& ref = val; // Type* const ref = &val;
c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。
//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
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;
}
3.10.4指针引用
在c语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样:
void fun(int**);
Type* pointer = NULL;
Type*& = pointer;//给指针变量取一个别名。
struct Teacher{
int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher){
*teacher = (Teacher*)malloc(sizeof(Teacher));
(*teacher)->mAge = 200;
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher){
teacher->mAge = 300;
}
void test(){
//创建Teacher
Teacher* teacher = NULL;
//指针间接赋值
AllocateAndInitByPointer(&teacher);
cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;
//引用赋值,将teacher本身传到ChangeAgeByReference函数中
AllocateAndInitByReference(teacher);
cout << "AllocateAndInitByReference:" << teacher->mAge << endl;
free(teacher);
}
3.10.5 常量引用
常量引用的定义格式:
const Type& ref = val;
常量引用注意:
字面量不能赋给引用,但是可以赋给const引用
const修饰的引用,不能修改。
void test01(){
int a = 100;
const int& aRef = a; //此时aRef就是a
//aRef = 200; 不能通过aRef的值
a = 100; //OK
cout << "a:" << a << endl;
cout << "aRef:" << aRef << endl;
}
void test02(){
//不能把一个字面量赋给引用
//int& ref = 100;
//但是可以把一个字面量赋给常引用
const int& ref = 100; //int temp = 200; const int& ret = temp;
}
[const引用使用场景]
常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数。
将函数的形参定义为常量引用的好处:
Ø 引用不产生新的变量,减少形参与实参传递时的开销。
Ø 由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。
如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用。
//const int& param防止函数中意外修改数据
void ShowVal(const int& param){
cout << "param:" << param << endl;
}
3.11内联函数
3.11.1内联函数基本概念
在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。
inline int func(int a){return ++;}
注意: 编译器将会检查函数参数列表使用是否正确,并返回值(进行必要的转换)。这些事预处理器无法完成的。
内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以空间换时间。
3.11.2类内部的内联函数
为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。
class Person{
public:
Person(){ cout << "构造函数!" << endl; }
void PrintPerson(){ cout << "输出Person!" << endl; }
}
构造函数Person,成员函数PrintPerson在类的内部定义,自动成为内联函数。
3.11.3 内联函数和编译器
对于任何类型的函数,编译器会将函数类型(包括函数名字,参数类型,返回值类型)放入到符号表中。同样,当编译器看到内联函数,并且对内联函数体进行分析没有发现错误时,也会将内联函数放入符号表。
当调用一个内联函数的时候,编译器首先确保传入参数类型是正确匹配的,或者如果类型不正完全匹配,但是可以将其转换为正确类型,并且返回值在目标表达式里匹配正确类型,或者可以转换为目标类型,内联函数就会直接替换函数调用,这就消除了函数调用的开销。假如内联函数是成员函数,对象this指针也会被放入合适位置。
类型检查和类型转换、包括在合适位置放入对象this指针这些都是预处理器不能完成的。
但是c++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
不能存在任何形式的循环语句
不能存在过多的条件判断 语句
函数体不能过于庞大
不能对函数进行取址操作
内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。
3.12函数的默认参数
c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
void TestFunc01(int a = 10, int b = 20){
cout << "a + b = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}
int main(){
//1.如果没有传参数,那么使用默认参数
TestFunc01();
//2. 如果传一个参数,那么第二个参数使用默认参数
TestFunc01(100);
//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01(100, 200);
return EXIT_SUCCESS;
}
注意点:
函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。
3.13 函数的占位参数
c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。
void TestFunc01(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
int main(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc01(10,20);
//正确调用
TestFunc01(10,20,30);
//正确调用
TestFunc02(10,20);
//正确调用
TestFunc02(10, 20, 30);
return EXIT_SUCCESS;
}
3.14函数重载
3.14.1 函数重载基本语法
3.14.1.1 函数重载基本语法
实现函数重载的条件:
同一个作用域
参数个数不同
参数类型不同
参数顺序不同
//1. 函数重载条件
namespace A{
void MyFunc(){ cout << "无参数!" << endl; }
void MyFunc(int a){ cout << "a: " << a << endl; }
void MyFunc(string b){ cout << "b: " << b << endl; }
void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}
//2.返回值不作为函数重载依据
namespace B{
void MyFunc(string b, int a){}
//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数
}
注意: 函数重载和默认参数一起使用,需要额外注意二义性问题的产生。
void MyFunc(string b){
cout << "b: " << b << endl;
}
//函数重载碰上默认参数
void MyFunc(string b, int a = 10){
cout << "a: " << a << " b:" << b << endl;
}
int main(){
MyFunc("hello"); //这时,两个函数都能匹配调用,产生二义性
return 0;
}
3.14.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类型
3.14.2extern C浅析
extern "C"的主要作用就是为了实现c++代码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而不是按c++的方式。
MyModule.h
#ifndef MYMODULE_H
#define MYMODULE_H
#include<stdio.h>
#if __cplusplus
extern "C"{
#endif
void func1();
int func2(int a,int b);
#if __cplusplus
}
#endif
#endif
MyModule.c
#include"MyModule.h"
void func1(){
printf("hello world!");
}
int func2(int a, int b){
return a + b;
}
TestExternC.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#if 0
#ifdef __cplusplus
extern "C" {
#if 0
void func1();
int func2(int a, int b);
#else
#include"MyModule.h"
#endif
}
#endif
#else
extern "C" void func1();
extern "C" int func2(int a, int b);
#endif
int main(){
func1();
cout << func2(10, 20) << endl;
return EXIT_SUCCESS;
}
4类和对象
4.1类和对象的基本概念
4.1.1 C和C++中struct区别
c语言struct只有变量
c++语言struct 既有变量,也有函数
4.1.2 类的封装
定义一个结构体用来表示一个对象所包含的属性,函数用来表示一个对象所具有的行为,这样我们就表示出来一个事物,在c语言中,行为和属性是分开的,
typedef struct _Person{
char name[64];
int age;
}Person;
typedef struct _Aninal{
char name[64];
int age;
int type; //动物种类
}Ainmal;
void PersonEat(Person* person){
printf("%s在吃人吃的饭!\n",person->name);
}
void AnimalEat(Ainmal* animal){
printf("%s在吃动物吃的饭!\n", animal->name);
}
int main(){
Person person;
strcpy(person.name, "小明");
person.age = 30;
AnimalEat(&person);
return EXIT_SUCCESS;
}
从这个案例我们应该可以体会到,属性和行为应该放在一起,一起表示一个具有属性和行为的对象。
封装应该再提供一种机制能够给属性和行为的访问权限控制住。
所以说封装特性包含两个方面,一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。
C++中的封装严格类型转换检测,让属性和行为绑定到一起
属性和行为作为一个整体来表示生活中的事物
控制权限public公有权限protected 保护权限 private私有权限
封装
\1. 把变量(属性)和函数(操作)合成一个整体,封装在一个类中
\2. 对变量和函数进行访问控制
访问权限
\1. 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
\2. 在类的外部(作用域范围外),访问权限才有意义:public,private,protected
\3. 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,外部不允许访问
访问属性 | 属性 | 对象内部 | 对象外部 |
---|---|---|---|
public | 共有 | 可访问 | 可访问 |
protected | 保护 | 可访问 | 不可访问 |
private | 私有 | 可访问 | 不可访问 |
//封装两层含义
//1. 属性和行为合成一个整体
//2. 访问控制,现实事物本身有些属性和行为是不对外开放
class Person{
//人具有的行为(函数)
public:
void Dese(){ cout << "我有钱,年轻,个子又高,就爱嘚瑟!" << endl;}
//人的属性(变量)
public:
int mTall; //多高,可以让外人知道
protected:
int mMoney; // 有多少钱,只能儿子孙子知道
private:
int mAge; //年龄,不想让外人知道
};
int main(){
Person p;
p.mTall = 220;
//p.mMoney 保护成员外部无法访问
//p.mAge 私有成员外部无法访问
p.Dese();
return EXIT_SUCCESS;
}
struct和class的区别:
class默认访问权限为private,struct默认访问权限为public.
class A{
int mAge;
};
struct B{
int mAge;
};
void test(){
A a;
B b;
//a.mAge; //无法访问私有成员
b.mAge; //可正常外部访问
}
4.1.3 将成员变量设置为private
可赋予客户端访问数据的一致性。
如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public权限的成员都是函数,客户在访问类成员时只会默认访问函数,不需要考虑访问的成员需不需要添加(),这就省下了许多搔首弄耳的时间。
可细微划分访问控制。
使用成员函数可使得我们对变量的控制处理更加精细。如果我们让所有的成员变量为public,每个人都可以读写它。如果我们设置为private,我们可以实现“不准访问”、“只读访问”、“读写访问”、“只写访问”。
使用Markdown编辑器