类的基本思想:数据抽象和封装 数据抽象是一种依赖接口和实现分离的编程技术 接口:类的用户所能执行的操作 实现:类的数据成员、接口函数的实现及其他私有函数的实现 封装:实现了类的接口和实现分离 封装后的类隐藏了实现细节; 类的用户只能使用接口而无法访问实现部分。 面向对象三大特性:封装、继承、多态
struct结构体→结构体变量 class类→对象
///c格式,struct
#include <stdio.h>
typedef struct _Date {
int y, m, d;
}Date;
int isValid(Date *pd) {
if (pd->y > 0 && pd->y < 9999 &&
pd->m >0 && pd->m < 13 &&
pd->d>0 && pd->d < 32)
return 1;
return 0;
}
void init(Date *pd) {
while (1) {
printf("请输入年 月 日:\n");
scanf("%d%d%d", &pd->y, &pd->m, &pd->d);
if (isValid(pd)) break;
printf("输入格式错误,重新输入!\n");
}
}
void print(Date *pd) {
printf("%d-%d-%d\n", pd->y, pd->m, pd->d);
}
int main()
{
Date d1;
init(&d1);
print(&d1);
return 0;
}
C语言的问题:结构体中所有东西都是可见和操作的
#include <iostream>
using namespace std;
class Date {
public:
void init() {
while (1) {
cout << "请输入年 月 日:" << endl;
cin >> y >> m >> d;
if (isValid()) break;
cout << "输入错误,重新输入!" << endl;
}
}
void print() {
cout << y << "-" << m << "-" << d << endl;
}
private:
bool isValid() {
if (y > 0 && y < 9999 && m>0 &&
m < 13 && d>0 && d < 32)
return true;
return false;
}
int y, m, d;
};
int main() {
Date d1;
d1.init();
d1.print();
return 0;
}
#include <iostream>
using namespace std;
class Stack {
public:
bool isEmpty()const { return topidx == 0; }
bool isFull()const { return topidx == size; }
void init(int len = 1024) {
ps = new int[len];
size = len;
topidx = 0;
}
void destroy() {
if (ps) delete[] ps;
ps = NULL;
}
int top()const {
return ps[topidx - 1];
}
void push(int data) {
ps[topidx++] = data;
}
void pop() {
topidx--;
}
private:
int *ps;
int topidx;
int size;
};
int main() {
Stack S;
S.init();
for (int i = 0; i < 5; i++) {
if (!S.isFull())
S.push(i);
}
while (!S.isEmpty()) {
cout << S.top() << " ";
S.pop();
}
//S.topidx = 10; //错误,无法修改
S.destroy();
return 0;
}
构造函数:每个类都定义了它的对象被初始化的方式,类通过一个或多个特殊的成员函数来控制其对象的初始化。(生成对象时自动调用) 析构函数:释放对象使用的资源。(对象销毁时自动调用)
构造函数:与类名相同,无返回,可以有参数 析构函数:~与类名相同,无参数,无返回
#include <iostream>
using namespace std;
class Stack {
public:
Stack(int len = 1024) {
ps = new int[len];
size = len;
topidx = 0;
cout << "构造函数run..." << endl;
}
~Stack() {
if (ps) delete[] ps;
ps = NULL;
cout << "析构函数run..." << endl;
}
bool isEmpty()const { return topidx == 0; }
bool isFull()const { return topidx == size; }
int top()const {
return ps[topidx - 1];
}
void push(int data) {
ps[topidx++] = data;
}
void pop() {
topidx--;
}
private:
int *ps;
int topidx;
int size;
};
int main() {
Stack S;
//S.init(); //不再需要,被构造函数替代了
for (int i = 0; i < 5; i++) {
if (!S.isFull())
S.push(i);
}
while (!S.isEmpty()) {
cout << S.top() << " ";
S.pop();
}
cout << endl;
//S.destroy(); //不再需要,被析构函数替代了
return 0;
}
类名a; //调用无参构造函数[不能写成类名a(),编译器会认为是函数声明] 类名a();//调用有参构造函数,a{xx}也可以 通过new在堆空间创建对象,同样会自动调用构造函数
#include <iostream>
using namespace std;
class Stack {
public:
Stack() { //无参构造函数
ps = new int[1024];
size = 1024;
topidx = 0;
cout << "Stack() run" << endl;
}
Stack(int len) { //带参构造函数
ps = new int[len];
size = len;
topidx = 0;
cout << "Stack(int len) run" << endl;
}
/*Stack(int len = 1024) {
ps = new int[len];
size = len;
topidx = 0;
}*/
~Stack() {
if (ps) delete[] ps;
ps = NULL;
cout << "析构函数run..." << endl;
}
bool isEmpty()const { return topidx == 0; }
bool isFull()const { return topidx == size; }
int top()const {
return ps[topidx - 1];
}
void push(int data) {
ps[topidx++] = data;
}
void pop() {
topidx--;
}
private:
int *ps;
int topidx;
int size;
};
int main() {
Stack S1; //调用无参构造 不能写 Stack S1();
Stack S2(100); //调用带参构造
Stack S3{ 10 };//调用带参构造
Stack *p1 = new Stack; //无参构造
Stack *p2 = new Stack(10);//带参构造
Stack *p3 = new Stack{ 10 };//带参构造
//对照:
int a1; //不能写 int a1(); 这是函数声明
int a2(10);
int a3{ 10 };
int *pa1 = new int;
int *pa2 = new int(10);
int *pa3 = new int{ 20 };
return 0;
}
模范标准库的string类: string s1; // 无参构造函数 string s2("abc");//有参构造函数
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
class mystring{
public:
mystring(){
ps = new char[1];
ps[0] = '\0';
}
mystring(const char *str){
int len = strlen(str) + 1;
ps = new char[len];
strcpy(ps,str);
}
/*
myString(const char *str = NULL) {
if (str == NULL) {
ps = new char[1];
ps[0] = '\0';
}
else {
int len = strlen(str) + 1;
ps = new char[len];
strcpy(ps, str);
}
}*/
~mystring(){
delete[] ps;
}
const char* c_str() const{
return ps;
}
private:
char *ps;
};
int main(){
string s1;
string s2("abc");
cout<<"-"<<s1.c_str()<<"-"<<endl;
cout<<s2.c_str()<<endl;
mystring ms1;
mystring ms2("abc");
cout<<"-"<<ms1.c_str()<<"-"<<endl;
cout<<ms2.c_str()<<endl;
}
析构函数:释放对象使用的资源(对象销毁时自动调用) 1.无参,无返回(不可重载) 2.若为提供,系统默认生成一个空的析构函数 通过delete销毁堆空间上的对象,同样会自动调用析构。 设计原则:自己申请的资源,自己负责释放
///析构函数初步 c代码
struct Stu {
char *name;
int age;
};
int main() {
//申请st1的内存
struct Stu *st1 =
(struct Stu*)malloc(sizeof(struct Stu));
//申请st1中name的内存
st1->name = (char*)malloc(sizeof(char)*20);
//释放st1中name的内存
free(st1->name);
//释放st1本身的内存
free(st1);
return 0;
}
C代码要逐级分别释放各自申请的资源
///C++代码
class Stu {
public:
Stu() {
name = new char[20];
age = 10;
}
~Stu() {
if (name) delete[] name;
}
private:
char *name;
int age;
};
int main() {
//申请st1的内存
Stu *st1 = new Stu;
delete st1;
return 0;
}
1.多个对象,按次序构造,析构次序相反 2.类中有成员变量也是类对象的时候,先运行成员类的构造函数,在运行本类的构造函数。析构次序与构造次序相反。 3.注意类中成员变量是类的指针类型的话,不会调用构造函数
#include <iostream>
#include <stdio.h>
using namespace std;
class A {
public:
A(int i = 0) {
num = i;
cout << "A()" << num << endl;
}
~A() { cout << "~A()" << num << endl; }
private:
int num;
};
class B {
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
};
int main() {
A a1;
A a2(1);
B b;
A *pa = new A[2]{ 2,3 }; //连续构造2次
delete[] pa; //连续析构2次
return 0;
}
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class myString {
public:
myString(const char *str = NULL) {
if (str == NULL) {
ps = new char[1];
ps[0] = '\0';
}
else {
int len = strlen(str) + 1;
ps = new char[len];
strcpy(ps, str);
}
cout << "myString构造" << endl;
}
~myString() {
if (ps) delete[] ps;
cout << "myString析构" << endl;
}
private:
char *ps;
};
class Stu {
public:
Stu() { cout << "Stu构造" << endl; }
~Stu() { cout << "Stu析构" << endl; }
private:
myString name;
int age;
};
int main() {
Stu st1;
return 0;
}
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
void fun() { A a2; }
int main() {
{
A a1;
}
//到这里,a1已经析构了。
fun();
//到这里,fun中的a2已经析构了。
cout << "----------" << endl;
int i = 0;
//注意a3的作用域
for (A a3; i < 3; i++) {
cout << i << endl;
}
cout << "----------" << endl;
//注意a4的作用域
for (int j = 0; j < 3; j++) {
A a4; //每次循环都会构造
cout << j << endl;
}//while循环也是如此
cout << "----------" << endl;
A *pa = new A; //构造
delete pa; //调用delete析构
return 0;
}
通常将一个类分为2个文件:类的声明写在类名.h;类的实现写在类名.cpp 一个类就是一个作用域 在类的外部定义成员函数时,返回值类型 类名::函数名(参数)
///myString.h
#ifndef MYSTRING_H
#define MYSTRING_H
#include <iostream>
class myString {
public:
myString(const char *str = NULL);
~myString();
const char* c_str()const;
private:
char *ps;
};
#endif // !MYSTRING_H
///调用
#include <iostream>
#include "myString.h"
using namespace std;
int main() {
myString s1;
return 0;
}
///myString.cpp
#include <string.h>
#include "myString.h"
//注意,默认参数要写在函数声明中
myString::myString(const char *str) {
if (str == NULL) {
ps = new char[1];
ps[0] = '\0';
}
else {
int len = strlen(str) + 1;
ps = new char[len];
strcpy(ps, str);
}
}
myString::~myString() {
delete[] ps;
}
const char* myString::c_str()const {
return ps;
}
类→对象,模具→产品 创建对象时:只有成员变量开辟内存,没有成员变量是,占用一个字节。成员函数并不占用对象的空间。 class 中的成员变量和C中的struct一样,要对齐、补齐。
#include <iostream>
using namespace std;
class A1 {};
class A2 { void fun() {} };
class A3 { int num; void fun() {} };
class A4 { int num; char name[6]; };
int main() {
cout << sizeof(A1) << endl; //1
cout << sizeof(A2) << endl; //1
cout << sizeof(A3) << endl; //4
cout << sizeof(A4) << endl; //12
return 0;
}
#include <iostream>
#include <string.h>
using namespace std;
class myString {
public:
myString(const char *str = NULL);
~myString();
private:
char *ps; int size;
};
myString::myString(const char *str) {
if (str == NULL) {
ps = new char[1];
ps[0] = '\0';
}
else {
int len = strlen(str) + 1;
ps = new char[len];
strcpy(ps, str);
}
}
myString::~myString() {
delete[] ps;
cout << "~myString()" << endl;
}
class Person {
public:
Person() { age = 20; p_fm = NULL; }
void set_fm(){
p_fm = new myString[2];
}
~Person() {
cout << "~Person()" << endl;
delete[] p_fm;
cout << "=========" << endl;
}
private:
int age; //年龄
myString name; //自己的名字
myString *p_fm; //父母的名字
};
int main() {
Person p1;
p1.set_fm();
return 0;
}
在内存怎么存储: person P1 在栈里面:
This 指针可以认为是顶层const,不能修改 为了区分num,对于外层的num用A::num表示,当然也可以用this->num
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
A(int num) { A::num = num; }
A(double num) { this->num = num; }
int num;
};
int main() {
A a1(20);
cout << a1.num << endl; //20
A a2(1.2);
cout << a2.num << endl; //1
return 0;
}
对象的内存和函数没有关系,怎么区分是哪一个num? show函数是如何知道返回的是a1还是a2的num? 可想像为:int A::show( A* const this); 调用a1.show(&a1); a2.show(&a2); 成员函数隐式地传递了一个当前对象的指针参数 this指针:指向的是本对象的地址
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
A(int num) { this->num = num; }
int show()const { return num; }
private:
int num;
};
int main() {
A a1(20);
cout << a1.show() << endl; //20
A a2(1);
cout << a2.show() << endl; //1
return 0;
}
有些类中,初始化和赋值的区别事关低层效率的问题,初始化的先后顺序和成员变量出现的先后次序一致
#include <iostream>
#include <string.h>
using namespace std;
class A {
public:
A(int n, double f) { num = n; fd = f; }
private:
int num;
double fd;
};
class B {
public:
//构造函数的初始值列表
B(int n, double f) :num(n), fd(f) {}
private:
int num;
double fd;
};
int main() {
A a(10, 1.2);
B b(10, 1.2);
return 0;
}
A类在创建对象时,先分别对num和fd调用默认初始化; 然后再通过赋值语句给num和fd赋值。 B类在创建对象时,直接初始化num和fd. A的流程:int num ; num = 10 ; B的流程: int num(10); 假如是类成员变量:A--> myString ma; ms = "abc"; B--> myString ms("abc");
#include <iostream>
#include <string.h>
using namespace std;
class B {
public:
B(double f) :fd(f), num(fd) {}
int get_num()const { return num; }
private:
int num;
double fd;
};
int main() {
B b(1.2);
cout << b.get_num() << endl;
//没有得到预期中的 1
return 0;
}
初始值列表对成员变量的初始化先后次序,与成员变量在类中出现的先后次序一致。 尝试用排序靠后的成员变量来初始化前面的成员变量,会得到未知的结果。
拷贝构造函数: class 类名{ 类名(const 类名 & another); }
赋值运算符重载: 类名{ 类名& operator = (const 类名 & 源对象) ....; return *this; }与拷贝构造类似
string s1("abc"); // 直接初始化
string s2("1234");
string s3(s2); // 直接初始化
string s4 = s2; // 拷贝初始化
string s5;
s5 = s2; // 赋值操作
class A {
public:
A(int n = 0) : num(n){}
A(const A &other) :num(other.num){}
private:
int num;
}
int main(){
A a1;
A a2(a1); //直接初始化
A a3 = a1; // 拷贝初始化
const A a4;
A a5 = a4; //参数没有const,就错误
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(int n = 0) :num(n) {}
A(const A &other) :num(other.num){ }
A& operator=(const A &other) {
num = other.num;
return *this;
}
private:
int num;
};
int main() {
A a1;
A a2;
a2 = a1; //赋值
return 0;
}
浅拷贝可能会造成内存泄漏、重析构
myString(const char * str = NULL){
ps = new char[len];
}
~myString(){
if(ps)
delete[] ps;
}
拷贝构造:myString s2 = s1;
等位拷贝:s1的ps指针赋给s2的ps
s1.ps 和 s2.ps同时指向同一个内存 s1,s2分别析构时,会两次释放同一内存
赋值运算符重载:
myString s1('AB')
myString s2('CD')
s2 = s1;
存在内存泄漏和重析构