1 类和对象
这段代码初始化一个长度为100的整型数组arr,并依次向数组中添加数字。然后分别输出添加前和添加后数组中的元素。
1.1 初级C语言示例
#include <stdio.h>
int main()
{
int arr[100] = {0};
//sizeof ??? strlen ???
int tail = 0;
int n = 10;
while(n--)
arr[tail++] = n;
int i = 0;
for(;i<tail; i++)
printf("%d, ", arr[i]);
printf("\n");
n = 5;
while(n--)
arr[tail++] = n;
for(i=0; i<tail; i++)
printf("%d, ", arr[i]);
printf("\n");
}
改进点:
- tail变量用来记录数组中已经填充的元素个数,最好能和arr进行绑定(结构体)
- 打印最好是能封装一下(函数)
1.2 C语言示例改进
//arr.c
#include "arr.h"
#include <stdio.h>
void init(ARR *arr)
{
arr->tail = 0;
}
void addtail(ARR *arr, int data)
{
arr->data[arr->tail++] = data;
}
void show(ARR *arr)
{
int i = 0;
for(;i<arr->tail; i++)
printf("%d, ", arr->data[i]);
printf("\n");
}
//arr.h
#ifndef _ARR_
#define _ARR_
typedef struct arr{
int data[100];
int tail;
}ARR;
void init(ARR *arr);
void addtail(ARR *arr, int data);
void show(ARR *arr);
#endif
//main.c
#include "arr.h"
int main()
{
ARR arr;
//ARR arr1;
init(&arr);
//init(&arr);
int n = 10;
while(n--)
addtail(&arr, n);
show(&arr);
ARR arr1;
init(&arr);
int i = 0;
for(;i<10; i++)
addtail(&arr1, i);
show(&arr1);
}
- 后续对其他数组初始化和添加打印时就可以方便很多,只要实现一次,未来不需要实现第二次
- 但是main函数中,init不知道传的是什么类型;addtail不太明确,可能是添加链表还是往数组中添加;想便利的时候不知道需要用到show,很多时候需要去阅读全局代码
- 方法和数据要绑定才是对的,别的变量无法使用到这种函数,这样的函数不会操作到别的类型对象
总之,拿到对象就应该拿到操作对象的方法
1.3 C语言面向对象设计思想顶级写法
期待的做法会报错,
//arr.c
#include "arr.h"
#include <stdio.h>
static void addtail(ARR *arr, int data) //static作用,别的对象无法操作
{
arr->data[arr->tail++] = data;
}
static void show(ARR *arr)
{
int i = 0;
for(;i<arr->tail; i++)
printf("%d, ", arr->data[i]);
printf("\n");
}
static void init(ARR *arr)
{
arr->tail = 0;
}
//arr.h
#ifndef _ARR_
#define _ARR_
typedef struct arr{
int data[100];
int tail;
void init(struct arr *arr);
void (*addtail)(struct arr *arr, int data);
void (*show)(struct arr *arr);
}ARR;
#endif
//main.c
#include "arr.h"
int main()
{
ARR arr;
arr.init(&arr); //段错误
int n = 10;
while(n--)
arr.addtail(&arr, n);
arr.show(&arr);
// arr.tail = 0;
arr.show(&arr);
}
C语言中初始化结构体是空结构体,指针的指向都是随机的,无法指认默认的函数,只能通过下面这种方式实现
//arr.c
#include "arr.h"
#include <stdio.h>
static void addtail(ARR *arr, int data) //static作用,别的对象无法操作
{
arr->data[arr->tail++] = data;
}
static void show(ARR *arr)
{
int i = 0;
for(;i<arr->tail; i++)
printf("%d, ", arr->data[i]);
printf("\n");
}
void init(ARR *arr)
{
arr->tail = 0;
arr->addtail = addtail;
arr->show = show;
}
//arr.h
#ifndef _ARR_
#define _ARR_
typedef struct arr{
int data[100];
int tail;
void (*addtail)(struct arr *arr, int data);
void (*show)(struct arr *arr);
}ARR;
void init(struct arr *arr);
#endif
//main.c
#include "arr.h"
int main()
{
ARR arr;
init(&arr);
int n = 10;
while(n--)
arr.addtail(&arr, n);
arr.show(&arr);
// arr.tail = 0; //也有缺陷,可以对结构体赋值,但是会造成其他功能的失效
arr.show(&arr);
}
- 也有缺陷,无法实现私有变量
- 无法初始变量的时候实现init初始化
- arr.tail还需要再传一遍arr
如何写的更面向对象
2 类与结构体差异
2.1 操作成员更安全
无法直接操作
可开发接口修改a及查看a
属于类的方法,那么成员变量都可以使用
3 类的申明
class 类名
{
private:
私有的数据和成员函数;
public:
公用的数据和成员函数;
protected:
保护的数据和成员函数
};
示例:
#include <stdio.h>
class A{
public:
A() //与类名相同的无参构造函数,即默认会主动调用的初始化函数
{
printf("construct !!!!!!!!\n");
a = 0; //赋初值
}
A(int data) //带参数的构造函数<------------
{
a = data;
}
~A() //析构函数,回收前打印
{
printf("ddddddddddddddddd~~~~~~~~~~~\n");
}
void show()
{
printf("xxxxxxxxxxxxx\n");
}
void setdata(int data)
{
a = data;
}
int getdata(void);
private:
int a;
};
int A::getdata(void)
{
return a; //默认可以使用类的变量及方法
}
int main()
{
//A x; //不带参数创建对象
A x(100); //带参数创建对象
// x.a = 100; //无法直接操作私有变量
// x.setdata(100); //可以通过接口操作私有变量
printf("%d\n", x.getdata() ); //可以通过接口获取私有变量
x.show();
}
//
3.1 类的成员函数(类内/类外实现)
构造函数(类的初始化,创建类时自动调用;初始化表,this指针)
默认构造函数 A();
拷贝构造函数 A(const A &x); //深浅拷贝问题
赋值构造函数 operator=?;
析构函数(类的解构,销毁类时自动调用)
~A();
浅拷贝示例
#include <stdio.h>
class A{
public:
A()
{
printf("A()\n");
}
A(int data)
{
printf("A(int data)\n");
}
~A(){
printf("A~~~~~~~~~~~~~\n");
}
};
int main()
{
//called when new object is created!
//A *p = new A(1000); //这种方式是在堆中
A x;
A m(100);
A y = 10;
A z = y; //调用了系统默认的拷贝构造函数,所以不打印
}
//执行效果:构造函数调了3次,析构函数调了4次
A()
A(int data)
A(int data)
A~~~~~~~~~~~~~
A~~~~~~~~~~~~~
A~~~~~~~~~~~~~
A~~~~~~~~~~~~~
在C++中,如果你没有手动为类编写拷贝构造函数和拷贝赋值运算符,那么编译器会默认为这个类生成一个拷贝构造函数和拷贝赋值运算符。这些默认的函数将执行浅拷贝,即只是简单地将原始对象的数据成员复制到新创建的对象中。
需要注意的是,当类中存在指针等引用类型成员时,进行浅拷贝可能会导致内存泄漏或其他问题。因此,在这种情况下,你应该手动为类编写拷贝构造函数和拷贝赋值运算符,以确保正确地进行深拷贝操作。
错误示例浅拷贝
#include <stdio.h>
#include <string.h>
class A{
public:
A()
{
printf("A()\n");
p = new char[10];
}
~A()
{
printf("~A()\n");
delete [] p;
}
private:
char *p;
};
int main()
{
A x;
A y = x; //双重释放析构函数,会报错。因为P在x中销毁了堆,但是y中还是引用的p的地址再次销毁就会报错
}
引入拷贝构造函数(深拷贝)
#include <stdio.h>
#include <string.h>
class A{
public:
A()
{
printf("A()\n");
p = new char[10];
strcpy(p, "hello");
printf("p: %s\n", p);
printf("p: %s\n", this->p); //当成员名和变量名冲突的时候,可以用this来取出成员
}
A(const A &x) //拷贝构造函数
{
printf("A(const A &x)\n");
p = new char[10];
strcpy(p, x.p); //拷贝内容放到自己的空间
}
~A()
{
printf("~A()\n");
delete [] p;
}
private:
char *p;
};
int main()
{
A x;
A y = x;
// y = x; //如果这样调用,不会激发任何构造函数,属于浅拷贝,以后会提到
}
//执行效果
A()
p:hello
p:hello
A(const A &x)
~A()
~A()
普通成员函数
略
3.2 常成员、常对象(C++推荐const而不用#define, mutable ) !!!
注:const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的(static 例外)。
常数据成员(构造函数初始化表赋值)
class A{
public: A():x(100) { }
const int x;
}
常成员函数
void func() const; //用于检查代码,无法进行变量修改
常对象
const A a;
示例:
#include <stdio.h>
class A{
public:
A(int a = 50, int data = 1000):b(data){
// b = data;
this->a = a;
printf("AAAAAAAAA\n");
}
~A(){
printf("~~~~~~~~~\n");
}
void show(void) const //常函数
{
printf("b = %d\n", b);
printf("a = %d\n", a);
// a++; //无法做变量修改
// b++; //无法做变量修改
}
private:
int a;
const int b; //常变量 无法做修改
};
int main()
{
A x(10);
x.show();
A y(100);
y.show();
A z;
z.show();
}
//执行效果
AAAAAAAAA
b = 1000
a = 10
AAAAAAAAA
b = 1000
a = 100
AAAAAAAAA
b = 1000
a = 50
~~~~~~~~~
~~~~~~~~~
~~~~~~~~~
3.3 静态成员(属于类不属于对象)
静态成员的申明
static int x;
static const int x = 10;
静态数据成员初始化
类外: static int A::x = 10;
静态成员函数
static void func(); //能访问静态成员
调用方法 A::func();
示例(无需创建对象即可使用的函数及变量)
#include <stdio.h>
class A{
public:
static void func(void)
{
printf("xxxxxxxxx\n");
}
static int data;
};
int A::data = 10; //必须初始化一下
int main()
{
A a;
a.func();
A::func(); //静态函数可以不用对象就可以调用
A x;
x.data = 100;
printf("x.data = %d\n", x.data);
A::data = 1000; //不需要命名空间也可以访问的变量
printf("x.data = %d\n", x.data);
printf("data = %d\n", data);
}
//执行结果
xxxxxxxxx
xxxxxxxxx
x.data = 100
x.data = 1000 //静态成员实际的地址都是一个
data = 1000
3.4 友元(破坏封装)
友元类 friend class B;
这表示当前类将类 B 声明为它的友元类,使得类 B 中的所有成员函数都可以访问当前类的私有成员。
友元函数 friend void func();
这表示当前类将一个名为 func 的全局函数声明为它的友元函数,使得 func 函数可以访问当前类的私有成员。
友元成员函数 friend void B::func();
这表示当前类将类 B 中的名为 func 的成员函数声明为它的友元函数,使得 B 类中的 func 函数可以访问当前类的私有成员。
友元函数示例:
//arr.c
#include "arr.h"
#include <stdio.h>
void ARR::addtail(int data)
{
this->data[tail++] = data;
}
void ARR::show(void)
{
int i = 0;
for(;i<tail; i++)
printf("%d, ", data[i]);
printf("\n");
}
//arr.h
#ifndef _ARR_
#define _ARR_
class ARR{
public:
ARR():tail(0){
// tail = 0;
}
void addtail(int data);
void show(void);
friend void rev(ARR &arr);
private:
int data[100];
int tail;
};
#endif
//main.c
#include "arr.h"
void rev(ARR &arr)
{
int i = 0;
for(;i<arr.tail/2; i++)
{
int tem = arr.data[i];
arr.data[i] = arr.data[arr.tail-i-1];
arr.data[arr.tail-i-1] = tem;
}
}
int main()
{
ARR arr;
arr.addtail(1);
arr.addtail(2);
arr.addtail(3);
arr.addtail(4);
arr.show();
//reverse
rev(arr); //破坏封装的方式,解决了其他函数对私有成员的访问
arr.show();
}
友元成员函数示例:
#include <stdio.h>
class A; //需要声明在前
class B{
public:
void printfA(A &x);
};
class A{
public:
A()
{
x = 100;
}
// friend class B; //友元类可以实现
friend void B::printfA(A &x); //如果不需要b的整个方法都访问A的打印,那么可以通过友元成员函数
private:
int x;
};
void B::printfA(A &x)
{
printf("%d\n", x.x);
}
int main()
{
A a;
// printf("%d\n", a.x); //报错
B b;
b.printfA(a);
}