真实存在的事物
多个对象抽取其共同特点形成的概念
静态特征提取出来的概念称为成员变量,又名属性
动态特征提取出来的概念称为成员函数,又名方法
在代码中先有类后有对象
一个类可以有多个对象
多个对象可以属于同一个类
class 类名
{
[访问权限修饰符:]
成员变量
成员函数
};
private: 私有的,当前类中可用,默认的
protected: 受保护的,当前类或子类中可用
public: 公共的,当前项目中可用
class Person{
private:
int age;
protected:
char sex[10];
public:
char name[50];
void eat()
{
cout << name << "吃饭" << endl;
}
void sleep();
};
void Person::sleep()
{
cout << name << "睡觉" << endl;
}
概念:即包装,将数据和方法封装在一起,加以权限区分使其可以保护内部,降低耦合度,便于使用
int a = 10;
int nums[5] = {1,2,3,4,5};
void fun()
{
xxx
}
class A
{
属性
函数
}
A a;
x.c文件
优点:
- 降低代码耦合度
- 提高代码复用率
- 便于使用
① 私有化所有属性
② 提供可以获取这些属性值与修改属性值的函数
示例:
#include <iostream>
#include <cstring>
using namespace std;
class Dog{
private:
char name[30];
int age;
public:
void set_name(char *n)
{
strcpy(name, n);
}
void set_age(int a)
{
age = a;
}
char *get_name()
{
return name;
}
int get_age()
{
return age;
}
void look_dor()
{
cout << name << "看门" << endl;
}
void eat()
{
cout << name << "吃" << endl;
}
};
int main(int argc, char *argv[])
{
Dog d1;
d1.set_name("大黄");
d1.set_age(2);
char *name = d1.get_name();
int age = d1.get_age();
cout << "姓名:" << name << " 年龄:" << age<< endl;
d1.look_dor();
Dog d2;
d2.set_name("旺财");
d2.set_age(1);
char *name2 = d2.get_name();
int age2 = d2.get_age();
cout << "姓名:" << name2 << " 年龄:" << age2 << endl;
d2.eat();
return 0;
}
//姓名:大黄 年龄:2
//大黄看门
//姓名:旺财 年龄:1
//旺财吃
请设计一个 Person 类,Person 类具有 name 和 age 属性,提供初始化函数(Init)
并提供对 name 和 age 的读写函数(set,get)
但必须确保 age 的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值
并提供方法输出姓名和年龄
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:
char name[50];
int age;
public:
void init(char *n,int a)
{
strcpy(name,n);
if(a < 0 || a > 100)
{
age = 0;
return;
}
age = a;
}
void set_name(char *n)
{
strcpy(name,n);
}
char* get_name()
{
return name;
}
void set_age(int a)
{
if(a < 0 || a > 100)
{
age = 0;
return;
}
age = a;
}
void print_info()
{
cout << name << endl << age << endl;
}
};
int main(int argc, char *argv[])
{
Person p;
// strcpy(p.name,"德玛");
// p.age = -18;
p.set_name("德玛");
p.set_age(-18);
p.print_info();
Person p2;
p2.init("光头强",18);
p2.print_info();
return 0;
}
//德玛
//0
//光头强
//18
4.2 练习2:分文件
设计立方体类(Cube)
其属性有长,宽,高
其函数有获取长宽高的函数,修改长宽高的函数
计算体积的函数
计算面积的函数
提示:
立方体的面积:2ab + 2ac + 2bc
体积:a * b * c
main.cpp
文件:
#include <iostream>
#include "cude.h"
using namespace std;
int main(int argc, char *argv[])
{
Cube c1;
c1.set_h(10);
c1.set_l(1);
c1.set_w(2);
c1.mj();
c1.tj();
return 0;
}
cube.cpp
文件
#include "cude.h"
#include <iostream>
using namespace std;
int Cube::get_l()
{
return l;
}
int Cube::get_w()
{
return w;
}
int Cube::get_h()
{
return h;
}
void Cube::set_l(int length)
{
l = length;
}
void Cube::set_w(int width)
{
w = width;
}
void Cube::set_h(int height)
{
h = height;
}
void Cube::mj()
{
int x = 2 * l * w + 2 * l * h + 2 * w * h;
cout << "立方体面积为:" << x << endl;
}
void Cube::tj()
{
int x = l * w * h;
cout << "立方体体积为:" << x << endl;
}
cube.h
文件
#ifndef CUDE_H
#define CUDE_H
class Cube
{
private:
int l;
int w;
int h;
public:
int get_l();
int get_w();
int get_h();
void set_l(int length);
void set_w(int width);
void set_h(int height);
void mj();
void tj();
};
#endif // CUDE_H
构造函数:类实例化对象的时候自动调用。
注意:
- 当一个类中
没有构造函数
,系统将默认
为其生成一个无参构造
- 如果一个类中有构造函数,系统将不会为其提供默认的无参构造
一个类
可以定义多个
构造函数,该类中的多个构造函数为重载关系
无参构造:
- 构造函数无形参列表
有参构造:
- 构造函数有形参列表
类名(形参列表)
{
该类对象赋初始值
}
注意:形参列表可有可无
#include <iostream>
using namespace std;
class A{
private:
int a;
public:
A()
{
cout << "无参构造被调用了" << endl;
}
A(int x)
{
cout << "有参构造被调用了" << endl;
}
void test()
{
cout << "test" << endl;
}
};
int main(int argc, char *argv[])
{
//隐式调用无参构造
//类名 对象名
A a1;
//显式调用无参构造
//类名 对象名 = 构造函数名();
A a2 = A();
//隐式调用有参构造
A a3(10);
//显式调用有参构造
A a4 = A(20);
//隐式转换(了解)
//当调用的构造函数中只有一个参数时可用
A a5 = 10;
cout << "********************" << endl;
//匿名对象
//没有对象名的对象
// 注意:一个匿名对象只能使用一次
A().test();
A(1000).test();
return 0;
}
//无参构造被调用了
//无参构造被调用了
//有参构造被调用了
//有参构造被调用了
//有参构造被调用了
//********************
//无参构造被调用了
//test
//有参构造被调用了
//test
对象生命周期结束的时候,自动调用析构函数。
注意:
一个类
只能有一个析构函数
- 如果
用户不提供
析构函数 编译器默认会提供一个空的析构函数
。经验:
一般不需要
自定义析构函数,但是如果类中有指针成员且指向堆区空间,这时必须实现析构函数
,在其中释放
指针成员指向的堆区空间
~类名()
{
}
注意:没有形参列表
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Data{
private:
int a;
int b;
char *msg;
public:
Data()
{
msg = NULL;
}
Data(int x, int y)
{
a = x;
b = y;
msg = NULL;
}
Data(int x, int y, char *m)
{
a = x;
b = y;
msg = (char *)malloc(50);
strcpy(msg, m);
}
~Data()
{
cout << "析构函数被调用了" << endl;
//判断msg堆区空间是否被释放
if(msg != NULL)
{
free(msg);
msg = NULL;
}
}
};
void fun01()
{
Data d1;
}
int main(int argc, char *argv[])
{
//fun01(); //析构函数被调用了 对象生命周期结束自动触发析构函数
Data d2; //析构函数被调用了 打开下面的死循环,不会被调用
// while(1);
return 0;
}
#include <iostream>
using namespace std;
class A{
private:
int a;
public:
A()
{
cout << "A无参构造函数被调用了" << endl;
}
A(int x)
{
a = x;
cout << a << "构造函数被调用了" << endl;
}
~A()
{
cout << a << "析构函数被调用了" << endl;
}
};
void fun01()
{
A a1(1);
A a2(2);
{
A a3(3);
A a4(4);
}
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
结果:
1构造函数被调用了
2构造函数被调用了
3构造函数被调用了
4构造函数被调用了
4析构函数被调用了
3析构函数被调用了
2析构函数被调用了
1析构函数被调用了
#include <iostream>
using namespace std;
class A{
private:
int a;
public:
A()
{
cout << "A无参构造函数被调用了" << endl;
}
A(int x)
{
a = x;
cout << a << "构造函数被调用了" << endl;
}
~A()
{
cout << a << "析构函数被调用了" << endl;
}
};
void fun01()
{
A a1(1);
A a2(2);
{
A a3(3);
A a4(4);
}
}
class B{
private:
int b;
A a;
public:
B(int x)
{
b = x;
cout << b << "B的构造函数被调用了" << endl;
}
~B()
{
cout << b << "B的析构函数被调用了" << endl;
}
};
void fun02()
{
B b(5);
}
int main(int argc, char *argv[])
{
//fun01();
fun02();
return 0;
}
//A无参构造函数被调用了
//5B的构造函数被调用了
//5B的析构函数被调用了
//4200699构造函数被调用了
拷贝构造在以下情况自动触发:
注意:
如果
用户不提供拷贝构造
编译器会提供一个默认
的拷贝构造(浅拷贝)。只有 类中有指针成员且指向堆区时 才有必要实现拷贝构造(深拷贝)。
浅拷贝与深拷贝:
- 浅拷贝:当类中的成员有指针成员,此时只拷贝地址
- 深拷贝:当类中的成员有指针成员,先开辟内存,再拷贝其值
类名(const 类名 &ob)
{
}
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Stu{
public:
int x;
public:
Stu()
{
cout << "Stu无参" << endl;
}
Stu(int a)
{
x = a;
cout << "Stu有参" << endl;
}
/*
拷贝构造
语法:
类名(const 类名& 对象名){
函数体
}
拷贝构造调用情况:
1,使用老对象初始化新对象
2,调用的函数时,该函数的形参为对象
3,函数的返回值是对象
*/
Stu(const Stu& s)
{
cout << "拷贝构造被调用了" << endl;
x = s.x;
}
};
void fun01()
{
Stu s1(10);
//将老对象初始化新对象,会触发拷贝构造
Stu s2 = s1;
cout << "s1.x = " << s1.x << endl;
cout << "s2.x = " << s2.x << endl;
s2.x = 100;
cout << "s1.x = " << s1.x << endl;
cout << "s2.x = " << s2.x << endl;
}
void test01(Stu s)
{
}
void fun02()
{
Stu s;
test01(s);
}
Stu test02()
{
static Stu s;
return s;
}
void fun03()
{
Stu s = test02();
}
int main(int argc, char *argv[])
{
fun01(); //情况1
fun02(); //情况2
cout << "--------------" << endl;
fun03(); //情况3
return 0;
}
//Stu有参
//拷贝构造被调用了
//s1.x = 10
//s2.x = 10
//s1.x = 10
//s2.x = 100
//Stu无参
//拷贝构造被调用了
//--------------
//Stu无参
//拷贝构造被调用了
注意:
- 在释放内存时,先释放p2 ,其指向的地址会被释放,但是p1的地址还是0x01,再次释放就会报错,所以浅拷贝会存在问题
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Person{
private:
int age;
char *name;
public:
Person(int a, char *n)
{
age = a;
name = (char *)malloc(50);
strcpy(name, n);
}
//浅拷贝
//系统提供的拷贝构造就是浅拷贝
//只拷贝地址,不拷贝值
Person(const Person& p)
{
name = p.name;
age = p.age;
}
void set_name(char *na)
{
strcpy(name, na);
}
void print_info()
{
cout << "姓名:" << name << "\t年龄:" << age << endl;
}
~Person()
{
if(name != NULL)
{
free(name);
name = NULL;
}
}
};
int main(int argc, char *argv[])
{
Person p1 = Person(18, "张三");
Person p2 = p1;
p1.print_info(); //姓名:张三 年龄:18
p2.print_info(); //姓名:张三 年龄:18
p2.set_name("李四");
p1.print_info(); //姓名:李四 年龄:18
p2.print_info(); //姓名:李四 年龄:18
return 0;
}
深拷贝会在堆区再开辟一块内存,将值拷贝到这块内存,即p1和 p2指向了不同的内存空间,释放时是各自释放各自的,不会存在问题
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Person{
private:
int age;
char *name;
public:
Person(int a, char *n)
{
age = a;
name = (char *)malloc(50);
strcpy(name, n);
}
//深拷贝
Person(const Person& p)
{
age = p.age;
name = (char *)calloc(1,50);
strcpy(name, p.name);
}
void set_name(char *na)
{
strcpy(name, na);
}
void print_info()
{
cout << "姓名:" << name << "\t年龄:" << age << endl;
}
~Person()
{
if(name != NULL)
{
free(name);
name = NULL;
}
}
};
int main(int argc, char *argv[])
{
Person p1 = Person(18, "张三");
Person p2 = p1;
p1.print_info(); //姓名:张三 年龄:18
p2.print_info(); //姓名:张三 年龄:18
p2.set_name("李四");
p1.print_info(); //姓名:张三 年龄:18
p2.print_info(); //姓名:李四 年龄:18
return 0;
}
构造函数:主要用于创建类的对象是给其属性赋初始值
在定义构造函数时,c++中提供了初始化列表的语法,以便于初始化成员变量的值。
类名(参数列表):成员名(参数名),成员名2(参数名2),... {
}
#include <iostream>
using namespace std;
class A{
private:
int a,b;
public:
A(){}
// A(int x,int y)
// {
// a = x;
// b = y;
// }
A(int x,int y):a(x),b(y){}
A(const A& obj)
{
a = obj.a;
b = obj.b;
}
~A()
{
}
void print_info()
{
cout << a << "\t" << b << endl;
}
};
class B{
public:
int c;
A a;
public:
B(int z,int x,int y):c(z),a(A(x,y))
{
}
};
int main(int argc, char *argv[])
{
A a(1,7); //1 7
a.print_info();
B b(1,2,3);
cout << b.c << endl; //1
b.a.print_info(); //2 3
return 0;
}
作用:禁止隐式转换
语法:
explicit 类名(形参列表):初始化列表
{
}
示例:
class C{
private:
int a;
public:
explicit C(int x):a(x){}
};
int main(int argc, char *argv[])
{
C c = 10;//构造函数的隐式转换
//当调用构造函数使用explicit修饰后防止隐式转换,此时上述代码报错
return 0;
}
当用 new
创建一个对象时,它就 在堆里为对象分配内存并调用构造函数完成初始化;
new 表达式的反面是 delete 表达式。 delete
表达式先调用析构函数,然后释放内存。正如 new 表达式返回一个指向对象的指针一样, delete 需要一个对象的地址。delete 只适用于由 new 创建的对象。
作用:
示例:
#include <iostream>
using namespace std;
void fun01()
{
int *p1 = new int;
*p1 = 100;
cout << *p1 << endl; //100
int *p2 = new int(200); //给p2的值初始化为200
cout << *p2 << endl; //200
delete p1;
delete p2;
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
作用:
示例:
#include <iostream>
using namespace std;
void fun02()
{
//int nums01[5] = {0};
int *nums01 = new int[5];
//int nums02[5] = {1,2,3,4,5};
int *nums02 = new int[5]{1,2,3,4,5};
for(int i = 0; i < 5; i++)
{
cout << nums01[i] << endl;
}
cout << "------------------" << endl;
for(int i = 0; i < 5; i++)
{
cout << nums02[i] << endl;
}
//delete nums01;//只会释放数组中第一个元素的内存
delete [] nums01;
delete [] nums02;
}
int main(int argc, char *argv[])
{
fun02();
return 0;
}
//7798976
//7798976
//0
//0
//-150994442
//------------------
//1
//2
//3
//4
//5
作用:
示例:
#include <iostream>
using namespace std;
class A{
public:
A()
{
cout << "A无参" << endl;
}
~A()
{
cout << "A析构" << endl;
}
};
int main(int argc, char *argv[])
{
//不会调用构造函数
// A *a = (A *)calloc(1,sizeof(A));
//不会调用析构函数
// free(a);
//会调用构造函数
A *a1 = new A();
//会调用析构函数
delete a1;
// while(1);
return 0;
}
数组中存放的是对象。
对象数组:必须 显示
调用构造函数初始化
示例:
#include <iostream>
using namespace std;
class A{
public:
int mA;
public:
A()
{
cout<<"A无参构造"<<endl;
}
A(int a)
{
mA = a;
cout<<"A有参构造mA="<<mA<<endl;
}
~A()
{
cout<<"A析构函数mA="<<mA<<endl;
}
};
void fun04()
{
//对象数组 必须显示调用构造函数初始化
A arr[5]={A(10),A(20),A(30),A(40),A(50)};
int n = sizeof(arr)/sizeof(arr[0]);
int i=0;
for(i=0;i<n;i++)
{
cout<<arr[i].mA<<" ";
}
cout<<endl;
}
int main(int argc, char *argv[])
{
fun04();
return 0;
}
//A有参构造mA=10
//A有参构造mA=20
//A有参构造mA=30
//A有参构造mA=40
//A有参构造mA=50
//10 20 30 40 50
//A析构函数mA=50
//A析构函数mA=40
//A析构函数mA=30
//A析构函数mA=20
//A析构函数mA=10
示例:
void fun05()
{
A *arr = new A[5]{A(10),A(20),A(30),A(40),A(50)};
int i=0;
for(i=0;i<5;i++)
{
cout<<arr[i].mA<<" ";
}
cout<<endl;
//delete arr;//只会释放第0个元素
delete [] arr;
}
//A有参构造mA=10
//A有参构造mA=20
//A有参构造mA=30
//A有参构造mA=40
//A有参构造mA=50
//10 20 30 40 50
//A析构函数mA=50
//A析构函数mA=40
//A析构函数mA=30
//A析构函数mA=20
//A析构函数mA=10
static 修饰的成员为静态成员。
建议使用 类名 调用静态成员
特点:
- 静态成员是
属于类
而不是对象。(所有对象共享)注意:
- 静态成员数据
不占对象的内存空间
。- 静态成员数据是属于类 而不是对象(多个对象共享一份静态成员数据)
- 静态成员数据 在
定义对象之前
就存在。静态成员数据在类中定义,类外初始化。
#include <iostream>
using namespace std;
class A{
public:
static int num;
int num03;
public:
A(){
}
};
//类外初始化
int A::num = 10;
int main(int argc, char *argv[])
{
A a1 = A();
a1.num03 = 20;
cout << sizeof(a1) << endl; //4
cout << a1.num << endl; //10
//对象a1.num03赋值
cout << "a1.num03 = " << a1.num03 << endl;//a1.num03 = 20
A a2;
//对象a2.num03没有赋值,所以是随机值
cout << "a2.num03 = " << a2.num03 << endl;//a2.num03 = 6422384
cout << "a1.num = " << a1.num << endl;//a1.num = 10
cout << "a2.num = " << a2.num << endl;//a2.num = 10
//通过a2修改num的值,a1.num也随之改变
a2.num = 100;
cout << "a1.num = " << a1.num << endl;//a1.num = 100
cout << "a2.num = " << a2.num << endl;//a2.num = 100
return 0;
}
1,属于类的,不属于对象,当前类中所有对象共有一份,不占用对象的内存空间
2,可以使用类名直接调用,也可以使用对象名调用
类名调用:
类名::静态成员变量名
对象调用:
对象名.静态成员变量名
3,在类中定义,在类外初始化,初始化时不关注访问权限修饰符,在类外使用时关注访问权限修饰符
使用 static 修饰的成员函数
特点:
- 静态成员函数中
只能使用静态成员
。
示例
#include <iostream>
#include "cstring"
using namespace std;
class Stu{
private:
static char c[50];
char name[50];
int age;
public:
//有参构造函数,方便对象初始化
Stu(char n[50],int a)
{
strcpy(name,n);
age = a;
}
void print_info()
{
cout << name << endl;
cout << age << endl;
cout << c << endl;
}
//注意静态成员函数中只能使用静态成员
static void test()
{
//cout << name << endl; //报错
//cout << age << endl;
cout << c << endl;
}
};
//当静态为私有的
//此时可以对其进行初始化,但是不能直接使用
char Stu::c[50] = "iot2302";
int main(int argc, char *argv[])
{
//隐式调用有参构造
Stu s01("张三",23);
Stu s02("李四",24);
Stu s03("王五",23);
s01.print_info();
s02.print_info();
s03.print_info();
//使用对象名调用静态成员(public权限下)
//strcpy(s01.c,"iot2303");
//建议使用类名调用静态成员(public权限下)
//cout << Stu::c << endl;
s01.print_info();
s02.print_info();
s03.print_info();
//静态成员函数,可以使用类名调用,也可以使用对象名调用
s01.test();
Stu::test();
return 0;
}
//张三
//23
//iot2302
//李四
//24
//iot2302
//王五
//23
//iot2302
//张三
//23
//iot2302
//李四
//24
//iot2302
//王五
//23
//iot2302
//iot2302
//iot2302
设计模式:
每一个设计模式是为了解决 一种特点的问题
概述:所属的类 只能实例化一个对象。
思想:
- 私有化构造函数
- 提供公共函数,返回该类对象
缺点:占用内存多
优点:线程安全
示例:
#include <iostream>
using namespace std;
class A{
private:
int x;
//实例化A的对象,此时是个指针
static A *a;
private:
A(){}
A(const A& a){
}
public:
//普通函数在外部只能通过对象调用,这个类在外部没法创建对象,
//所以只能创建为公共静态函数,在外部通过类调用。
//这个函数是为了返回该类的对象,如果只返回A,还是会触发拷贝构造(旧对象给新对象初始化),
//因为拷贝构造函数已经私有化,所以只能返回类的引用
static A& getInstance()
{
//返回该类的对象(对a取值,就是对象)
return *a;
}
};
// 静态成员变量 类外初始化,开辟内存,所以是地址,用指针接收
A *A::a = new A();
void fun01()
{
//返回的是A的引用个,所以接收的也是A的引用这种类型
A& a1 = A::getInstance();
A& a2 = A::getInstance();
A& a3 = A::getInstance();
//打印这3个对象的地址,相同说明实例化的是同一个对象
cout << &a1 << endl; //0x1e7030
cout << &a2 << endl; //0x1e7030
cout << &a3 << endl; //0x1e7030
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
缺点:线程不安全
优点:占内存少
示例:
class B{
private:
int x;
static B* b;
private:
B(){}
B(const B& b){}
public:
static B& getInstance()
{
//判断是否已经创建对象
if(b == NULL)
{
b = new B();
}
return *b;
}
};
B *B::b = NULL;
int main(int argc, char *argv[])
{
B& b1 = B::getInstance();
B& b2 = B::getInstance();
B& b3 = B::getInstance();
cout << &b1 << endl; //0xf37040
cout << &b2 << endl; //0xf37040
cout << &b3 << endl; //0xf37040
return 0;
}