结构体详解
- 结构体是不同数据类型的集合。
结构体的好处:
1) 我们在写一个关于管理学生信息的代码,如果我们有4000个学生要管理。我们要管理他们的姓名,学号,电话号码,身份证,父母姓名,父母电话这些信息。如果按照普通方法,我们要创建6*4000 = 24000这么多的变量,然而事实上这是不可能的,到时候直接能把你累秃了。
2) Ok!如果有的同学说,不怕,不久2400个变量吗?我不怕,我计算机也强大,没事!那你牛逼。但是没如果我们想写一个神经网络的代码,例如我们要写VGG19后面的3个全连接层之间的连接权值w,想想得有多少?老铁你还敢硬刚?
我知道你们不敢,这个时候就需要使用结构体了。只要定义一个结构体,每个学生都有一个编号,然后我们通过定义一个指向结构体的指针()来实现对每个学生信息的管理。这一点大家可能还不明白。
如果我们想管理VGG19后面的3个全连接层的权值w,那么我们先将对应层的变量都定义到一个结构体中:
// --- 输出层节点。包含以下数值:---
// 1.value: 节点当前值;
// 2.delta: 与正确输出值之间的delta值;
// 3.rightout: 正确输出值
// 4.bias: 偏移量
// 5.bDeltaSum: bias的delta值的累积,每个节点一个
typedef struct outputNode // 输出层节点
{
double value, delta, rightout, bias, bDeltaSum;
}outputNode;
然后定义一个指向结构体的指针:
outputNode* outputLayer[outnode]; // outnode输出层节点数
然后通过指针的方式就可以操作每个变量了:
hiddenLayer[i][j] = new hiddenNode();
hiddenLayer[i][j]->bias = get_11Random();
for (int k = 0; k < outnode; k++)
{
hiddenLayer[i][j]->weight.push_back(get_11Random());
hiddenLayer[i][j]->wDeltaSum.push_back(0.f);
一、结构体数据类型的定义
- 结构体的结构定义:
• struct 结构体名
{
成员名;
}
struct Student
{
int ID;
char name[20];
}stu;
后面的stu就相当于类中定义一个类的对象
结构体的一些注意事项:
- 结构类型与结构变量是两个不同的概念,其区别如同int类型与int变量之间的区别。
int x = 10;//其中 int是类型,x是变量
struct student stu;//其中struct student是类型,stu是数据的变量 - 结构类型中的成员名,可以与程序中的变量名相同,它代表不同的对象,互不干扰。
#include <iostream>
using namespace std;
struct Student
{
int ID;
char name[20];
}stu;
int main()
{
int ID = 10;
cout << ID << endl;
}
这个时候是可以输出的,没有问题
- 结构体中的数据类型,既可以是基本的数据类型(int char double等),也可以是结构体数据类型。
#include <iostream>
using namespace std;
struct Student
{
int ID;
char name[20];
struct teacher
{
int age;
}th;
}stu;
int main()
{
int ID = 10;
cout << ID << endl;
}
结构体中定义结构体也没有问题
二、结构体的使用
简单操作,以小见大。
针对上述struct student的结构体,我简单试用一下:
首先,要想使用类一样,我们先定义一个对象:
struct Student stu;
#include <iostream>
using namespace std;
struct Student
{
int ID;
char name;
struct teacher
{
int age;
};
};
int main()
{
int ID = 10;
struct Student stu;
stu.ID = 20;//这里的这个“.”我们可以理解成“的”的意思,即stu的ID
stu.name = 'i';
//或者使用如下方式
struct Student stu = { 666,'ch' };
cout << ID << endl;
}
这个时候,如果我们量使用结构体中的结构体,我们该怎么办呢?
#include <iostream>
using namespace std;
struct Student
{
int ID;
char name;
struct teacher
{
int age;
}th;
};
int main()
{
int ID = 10;
struct Student stu;
stu.ID = 20;
stu.name = 'i';
//或者使用如下方式
struct Student stu = { 666,'ch' };
//使用结构体中的结构体
stu.th.age = 26;//这时,我们在teacher后面也定义了一个th,这个th其实就是相当于建立一个teacher的对象。这句话我们可以理解成:student里面有一个teacher,而这个teacher里面又有一个age。
cout << ID << endl;
}
当我们在struct前面加一个typedef的时候,如果按照之前的用法,肯定会报错
#include <iostream>
using namespace std;
typedef struct Student
{
int ID;
char name;
struct teacher
{
int age;
}th;
}stu;
int main()
{
int ID = 10;
stu.ID = 20;
stu.name = 'i';
//或者使用如下方式
struct Student stu = { 666,'ch' };
//使用结构体中的结构体
stu.th.age = 26;//这时,我们在teacher后面也定义了一个th,这个th其实就是相当于建立一个teacher的对象。
cout << ID << endl;
}
- 这是因为:此时stu已经有一个变量变成了一个类型。这个时候的stu相当于我们熟知的int、double
long等。这个时候如果我们这样用:stu stud;那么这个stud就是一个变量
#include <iostream>
using namespace std;
typedef struct Student
{
int ID;
char name;
struct teacher
{
int age;
}th;
}stu;
int main()
{
stu stud = {6666,'f'};
cout << stud.ID << endl;
}
2.1 上面的这些操作都不算骚,最骚的是使用指针来操作。
之所以说指针用起来骚,是因为我们以后在实现具体工程的时候,非常方便,特别是对于那种需要多次调用的时候。
通过指针可以来访问结构变量的成员,与知己使用结构变量的效果一样。一般地说,如果指针变量pointer已指向结构变量var,则用一下两种形式:
指针即可以指向任何的类型,即可以指向基本的数据类型,指向数组,也可以指向结构体。
var.成员
pointer->成员
现在我们用代码演示一下
#include <iostream>
using namespace std;
typedef struct Student
{
int ID;
char name;
struct teacher
{
int age;
}th;
}stu;
int main()
{
stu stud = {6666,'f'};
cout << stud.ID << endl;
//现在我们定义一个变量指向stdu变量。
stu* p_stud = &stud;
//想在我想通过指针操作stud中的成员变量
p_stud->ID = 1698;
p_stud->name = 'l';
cout << stud.ID <<" and "<<stud.name<< endl;
}
好了,看到了指针的操作
下面是更骚气的操作了!!
我们现在模仿使用过指针给神经网络的神经元之间的权值进行初始化赋值。
#include <iostream>
#include <vector>
using namespace std;
#define innode 2 //输入结点数
#define hidenode 4 //隐含结点数
#define hidelayer 1 //隐含层数
#define outnode 1 //输出结点数
#define learningRate 0.9//学习速率,alpha
// --- -1~1 随机数产生器 --- 内联函数,短小,提高效率
inline double get_11Random() // -1 ~ 1
{
return ((2.0*(double)rand() / RAND_MAX) - 1);
}
// --- sigmoid 函数 --- 内联函数,短小,提高效率
inline double sigmoid(double x)
{
double ans = 1 / (1 + exp(-x));
return ans;
}
//模仿神经网网络权值初始化
// --- 输入层节点。包含以下分量:---
// 1.value: 固定输入值;
// 2.weight: 面对第一层隐含层每个节点都有权值;
// 3.wDeltaSum: 面对第一层隐含层每个节点权值的delta值累积
typedef struct inputNode
{
double value;
vector<double> weight, wDeltaSum;
}inputNode;
typedef struct hiddenNode // 隐含层节点
{
double value, delta, bias, bDeltaSum;
vector<double> weight, wDeltaSum;
}hiddenNode;
// --- 单个样本 ---
typedef struct sample
{
vector<double> in, out;
}sample;
int main()
{
//指针操作,后面的[]中的表示节点数,输入层的节点数和隐含层的节点数,两层之间每个节点之间都有连接,因为是全连接层。因此需要逐一初始化。
inputNode* inputLayer[innode]; // 输入层(仅一层)
hiddenNode* hiddenLayer[hidelayer][hidenode]; // 隐含层(可能有多层)
//实现隐含层和输入层的初始化
for (int i = 0; i < innode; i++)
{
inputLayer[i] = new inputNode();
for (int j = 0; j < hidenode; j++)
{
inputLayer[i]->weight.push_back(get_11Random());
inputLayer[i]->wDeltaSum.push_back(0.f);
}
}
// 初始化隐藏层
for (int i = 0; i < hidelayer; i++)
{
if (i == hidelayer - 1)
{
for (int j = 0; j < hidenode; j++)
{
hiddenLayer[i][j] = new hiddenNode();
hiddenLayer[i][j]->bias = get_11Random();
for (int k = 0; k < outnode; k++)
{
hiddenLayer[i][j]->weight.push_back(get_11Random());
hiddenLayer[i][j]->wDeltaSum.push_back(0.f);
}
}
}
else
{
for (int j = 0; j < hidenode; j++)
{
hiddenLayer[i][j] = new hiddenNode();
hiddenLayer[i][j]->bias = get_11Random();
for (int k = 0; k < hidenode; k++) { hiddenLayer[i][j]->weight.push_back(get_11Random()); }
}
}
}
}
看图,输入层与隐含层之间的连接权值已经被随机初始化了