本文将大量涉及C语言高级操作,如函数指针、结构体指针、二级指针、指针频繁引用解引用、typedef、static、inline和C语言项目结构等知识,请确保自己不会被上述知识强暴,如果没有这顾虑,请尽情享受~
渊源
一开始时候,我是不知道这个技术的。在某一天我在刷B站的时候,看到一个作者为“一点五编程”的视频。他提出了一种编程思想,命名为“一点五编程”。其中:
"一"指的是模块化思想
“点五”指的是(*p)->f(p)技巧
我一看,好像是一种高端的技巧哇,于是开始看他的视频,发现讲解这一技术核心的视频全都是充电的!!!好吧,那我只好翻你文档看了,找到了他的个人博客。唔,好像什么都写了,但好像少点什么,,,哦,没教我到底怎么组织文件。
然后我继续翻网页,在CSDN上发现三篇文章,讲的是对“一点五编程”的解读。但是后来在自己实操过程中,还是发现了其中的错误。
看了这么多文章和视频,脑子一拍,这不就是面向对象的编程范式吗,只不过C语言是面向过程的语言,没有现成的面向对象的组件,但是思想上完全就是OOP那套嘛!
于是我开始自己扒,终于,也是让我扒出来了~
在文章的最后我会放上一个循迹小车的项目,当然,功能上伪实现(狗头),那接下来先讲下这门技术的基本理论和开发流程吧
理论
面向对象编程
我们先来说一下面向对象编程是什么:
面向对象编程是一种编程范式,它通过定义类和对象来组织和设计程序。
在面向对象编程中,程序猿通过创建类来定义数据结构和行为,通过创建对象来实例化这些类,并通过对象之间的交互来实现程序的功能。这种方法使得程序的结构更加清晰和易于维护。
面向对象编程有几个特性,分别是:封装、继承、多态、抽象,这里就不再说了,只要知道本文会体现就行,(纠结)因为毕竟还是挺难理解的,我也讲不明白,可以看看别的大佬的文章。
为什么要把面向对象编程拉出来说呢?众所众知,嵌入式是一个软硬件结合的学科,这就会存在一个问题,就是我们会非常在乎硬件的实现,上层的功能实现就实现了,也不会在乎开发的结构、后期的维护等,这一点在初学者身上体现的淋漓尽致。
而面向对象编程就致力于让程序更加模块化,通过继承和多态,使得大量代码复用,它还有模拟现实世界中对象和关系的能力。这样,就为开发者提供了一种自顶向下的开发思路。同时,它将上层实现与下层驱动相隔离,让更换开发平台变得简单。
流程
我将整个C语言面向对象编程的开发分为三个阶段,分别是声明、实现和使用。
声明
声明阶段又可以分为五个步骤,这些都是在头文件中写入的,分别是:
- 声明接口函数
- 定义接口结构体
- 定义类结构体
- 定义类型转换内联函数
- 声明方法实例
其中,声明接口函数、定义接口结构体和定义类型转换内联函数仅需书写一次,另外两个步骤可以根据实际需求定义更多的类和方法实例。
声明接口函数
在这里,接口就是类的行为方法集,控制整个类的行为方式。以循迹模块为例,读取循迹信息就是它的一个行为;以电机驱动模块为例,控制电机停转、正转、反转和控制转动速度就是它的一系列行为。我们首先要思考我们所抽象出的类有哪些行为方法,写成下面形式:
typedef int (*Method0FnT)(void* self, ...);
typedef int (*Method1FnT)(void* self, ...);
.
.
.
typedef int (*MethodnFnT)(void* self, ...);
定义接口结构体
接下来,我们要将上面的接口函数放到一个接口结构体中,方便由各个类使用:
typedef struct
{
Method0FnT method0Fn;
Method1FnT method1Fn;
.
.
.
MethodnFT methodnFn;
} MethodsT;
定义类结构体
完成上面步骤后,一个类的方法集就总结好了,再由方法集和类的各个属性组成完整的类,这里一定要注意,方法集指针一定要放在类结构体的第一个,否则会出现错误:
typedef struct
{
MethodsT* methods;
Type attribute0;
Type attribute1;
.
.
.
Type attributen;
} Class;
定义类型转换内联函数
这里是我们实现多态这一特性最核心的步骤,写成如下格式:
static inline int method0Fn(void* self, ...)
{
return (*(MethodsT**)self)->method0Fn(self, ...);
}
static inline int method1Fn(void* self, ...)
{
return (*(MethodsT**)self)->method1Fn(self, ...);
}
.
.
.
static inline int methodnFn(void* self, ...)
{
return (*(MethodsT**)self)->methodnFn(self, ...);
}
使用上面的语句,我们能够将(*p)->f(p)
改写为f(p)
的形式,而且不需要管类的具体函数实现。这里我们将指向类的一级指针强制类型转换为指向接口的二级指针,再解引用就得到了一个仅指向接口的一级指针,再用成员访问符使用接口函数。
这个过程中,要将指向类的一级指针强制类型转换为指向接口的二级指针,就需要类的起始地址与接口的起始地址相同,也就是为什么上面说方法集的指针一定要放在类结构体的第一个,这样指向接口的二级指针解引用后才会指向接口。

声明方法实例
上面定义了抽象的接口和类,该到这个接口函数的具体实现了,当然,还要写上类初始化函数的声明:
int classMethod0(void* self, ...);
int classMethod1(void* self, ...);
.
.
.
int classMethodn(void* self, ...);
int classInit(void* self, ...);
实现
头文件中内容就完成了,下面是具体的相关函数的实现了,下面部分都在源文件中写入,分为三个步骤:
- 定义方法实例
- 定义接口实例
- 定义类初始化函数
三个步骤的内容均由头文件中的声明限制。
定义方法实例
方法的实例我们已经在头文件中声明过了,在这里我们进行这些方法实例的定义:
int classMethod0(void* self, ...);
{
Class* pClass= (Class*)self;
//具体内容实现
//异常处理
return 1;
}
int classMethod1(void* self, ...)
{
Class* pClass= (Class*)self;
...
return 1;
}
.
.
.
int classMethodn(void* self, ...)
{
Class* pClass= (Class*)self;
...
return 1;
}
定义接口实例
具体的方法已经有了,接下来我们要实现具体的接口了,将方法实例的函数指针传入到接口结构体中:
static MethodsT classMethods=
{
.method0Fn= classMethod0,
.method1Fn= classMethod1,
.
.
.
.methodnFn= classMethodn
}
定义类的初始化函数
最后我们编写所需类的初始化的函数,类的属性值将通过初始化函数传入:
int classInit(void* self, ...)
{
Class* pClass= (Class*)self;
pClass->methods= &classMethods;
pClass->attribute0= ...;
pClass->attribute1= ...;
.
.
.
pClass->attributen= ...;
//其他初始化内容
//异常处理
return 1;
}
使用
使用起来就简单了,首先我们要生成类的实例,然后使用初始化函数进行类初始化,然后,使用!
//生成实例可以放在main.h中或者主函数前或者主函数开头
Class class
//初始化要放在开头
classInit(&class, ...);
//使用方法就放在任何你需要其执行的地方即可
method0Fn(&class, ...);
method1Fn(&class, ...);
.
.
.
methodnFn(&class, ...);
就这样,我们的所有功能就实现啦,我相信你一定学会了!(狗头)
附录
循迹小车的伪实现,会体现出上面没有提及的继承特性
trace.h
#ifndef _TRACE_H
#define _TRACE_H
typedef int (*TraceReadFnT)(void* self, int* result);
typedef struct
{
TraceReadFnT traceReadFn;
} TraceMethodsT;
typedef struct
{
TraceMethodsT* pTraceMethods;
int tracePin;
} Trace;
static inline int traceReadFn(void* self, int* result)
{
return (*(TraceMethodsT**)self)->traceReadFn(self, result);
}
int traceRead(void* self, int* result);
int traceInit(void* self, int pin);
#endif
trace.c
#include <stdlib.h>
#include <stdio.h>
#include "trace.h"
int traceRead(void* self, int* result)
{
Trace* pTrace= (Trace*)self;
int num= rand()%2;
if(num!=0&&num!=1)
return 0;
*result=num;
printf("trace result is %d\n",*result);
return 1;
}
static TraceMethodsT TraceMethods=
{
.traceReadFn= (TraceReadFnT)traceRead
};
int traceInit(void* self, int pin)
{
Trace* pTrace= (Trace*)self;
pTrace->pTraceMethods= &TraceMethods;
pTrace->tracePin= pin;
printf("Trace object inited successfully\n");
return 1;
}
motor.h
#ifndef _MOTOR_H
#define _MOTOR_H
typedef enum
{
MOTOR_STOP,
MOTOR_FORWARD,
MOTOR_REVERSE
} MotorStat;
typedef int (*MotorStopFnT)(void* self);
typedef int (*MotorForwardFnT)(void* self, int speed);
typedef int (*MotorReverseFnT)(void* self, int speed);
typedef int (*MotorControlSpeedFnT)(void* self, int speed);
typedef struct
{
MotorStopFnT motorStopFn;
MotorForwardFnT motorForwardFn;
MotorReverseFnT motorReverseFn;
MotorControlSpeedFnT motorControlSpeedFn;
} MotorMethodsT;
typedef struct
{
MotorMethodsT* pMotorMethods;
int motorPinA;
int motorPinB;
int motorPinControlSpeed;
MotorStat motorStat;
int motorSpeed;
} Motor;
static inline int motorStopFn(void* self)
{
return (*(MotorMethodsT**)self)->motorStopFn(self);
}
static inline int motorForwardFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorForwardFn(self, speed);
}
static inline int motorReverseFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorReverseFn(self, speed);
}
static inline int motorControlSpeedFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorControlSpeedFn(self, speed);
}
int motorStop(void* self);
int motorForward(void* self, int speed);
int motorReverse(void* self, int speed);
int motorControlSpeed(void* self, int speed);
int motorInit(void* self, int pinA, int pinB, int pinControlSpeed);
#endif
motor.c
#include <stdio.h>
#include "motor.h"
int motorStop(void* self)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_STOP;
pMotor->motorSpeed= 0;
printf("motor stopped\n");
return 1;
}
int motorForward(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_FORWARD;
pMotor->motorSpeed= speed;
printf("motor turned forward, speed is %d\n",pMotor->motorSpeed);
return 1;
}
int motorReverse(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_REVERSE;
pMotor->motorSpeed= speed;
printf("motor turned reverse, speed is %d\n",pMotor->motorSpeed);
return 1;
}
int motorControlSpeed(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorSpeed= speed;
printf("motor speed is turned to %d\n",pMotor->motorSpeed);
return 1;
}
static MotorMethodsT MotorMethods=
{
.motorStopFn= (MotorStopFnT)motorStop,
.motorForwardFn= (MotorForwardFnT)motorForward,
.motorReverseFn= (MotorReverseFnT)motorReverse,
.motorControlSpeedFn= (MotorControlSpeedFnT)motorControlSpeed
};
int motorInit(void* self, int pinA, int pinB, int pinControlSpeed)
{
Motor* pMotor= (Motor*)self;
pMotor->pMotorMethods= &MotorMethods;
pMotor->motorPinA= pinA;
pMotor->motorPinB= pinB;
pMotor->motorPinControlSpeed= pinControlSpeed;
pMotor->motorStat= MOTOR_STOP;
pMotor->motorSpeed= 0;
printf("motor object inited successfully\n");
return 1;
}
trail_car.h
#ifndef _TRAIL_CAR_H
#define _TRAIL_CAR_H
#include "trace.h"
#include "motor.h"
enum TrailCarStat
{
CAR_STOP,
CAR_RUN
};
typedef int (*TrailCarRunFnT)(void* self, int speedStraight, int speedTurnFast, int speedTurnLow);
typedef int (*TrailCarStopFnT)(void* self);
typedef struct
{
TrailCarRunFnT trailCarRunFn;
TrailCarStopFnT trailCarStopFn;
} TrailCarMethodsT;
typedef struct
{
TrailCarMethodsT* pTrailCarMethods;
Trace* trailCarTrace0;
Trace* trailCarTrace1;
Trace* trailCarTrace2;
Trace* trailCarTrace3;
Trace* trailCarTrace4;
Motor* trailCarMotorLeft;
Motor* trailCarMotorRight;
enum TrailCarStat trailCarStat;
} TrailCar;
static inline int trailCarRunFn(void* self, int speedStraight, int speedTurnFast, int speedTurnLow)
{
return (*(TrailCarMethodsT**)self)->trailCarRunFn(self, speedStraight, speedTurnFast, speedTurnLow);
}
static inline int trailCarStopFn(void* self)
{
return (*(TrailCarMethodsT**)self)->trailCarStopFn(self);
}
int trailCarRun(void* self, int speedStraight, int speedTurnFast, int speedTurnLow);
int trailCarStop(void* self);
int trailCarInit
(
void* self,
Trace* trace0,
Trace* trace1,
Trace* trace2,
Trace* trace3,
Trace* trace4,
Motor* motorLeft,
Motor* motorRight
);
#endif
trail_car.c
#include <stdio.h>
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
int trailCarRun(void* self, int speedStraight, int speedTurnFast, int speedTurnLow)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->trailCarStat= CAR_RUN;
int result0, result1, result2, result3, result4;
traceReadFn(pTrailCar->trailCarTrace0, &result0);
traceReadFn(pTrailCar->trailCarTrace1, &result1);
traceReadFn(pTrailCar->trailCarTrace2, &result2);
traceReadFn(pTrailCar->trailCarTrace3, &result3);
traceReadFn(pTrailCar->trailCarTrace4, &result4);
if(result0==1&&result1==1&&result2==1&&result3==1&&result4==1)
{
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
printf("trail car stopped\n");
}
else if(result0==0&&result1==0&&result2==1&&result3==0&&result4==0)
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedStraight);
motorForwardFn(pTrailCar->trailCarMotorRight, speedStraight);
printf("trail car went straight\n");
}
else if(result0==0&&result1==1&&result2==0&&result3==0&&result4==0)
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedTurnLow);
motorForwardFn(pTrailCar->trailCarMotorRight, speedTurnFast);
printf("trail car turned left\n");
}
else if(result0==0&&result1==0&&result2==0&&result3==1&&result4==0)
{
motorForwardFn((void*)(pTrailCar->trailCarMotorLeft), speedTurnFast);
motorForwardFn((void*)(pTrailCar->trailCarMotorRight), speedTurnLow);
printf("trail car turned right\n");
}
else
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedStraight);
motorForwardFn(pTrailCar->trailCarMotorRight, speedStraight);
printf("trail car went straight\n");
}
return 1;
}
int trailCarStop(void* self)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->trailCarStat= CAR_RUN;
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
printf("trail car stopped\n");
return 1;
}
static TrailCarMethodsT TraceMethods=
{
.trailCarRunFn= (TrailCarRunFnT)trailCarRun,
.trailCarStopFn= (TrailCarStopFnT)trailCarStop
};
int trailCarInit
(
void* self,
Trace* trace0,
Trace* trace1,
Trace* trace2,
Trace* trace3,
Trace* trace4,
Motor* motorLeft,
Motor* motorRight
)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->pTrailCarMethods= &TraceMethods;
pTrailCar->trailCarTrace0= trace0;
pTrailCar->trailCarTrace1= trace1;
pTrailCar->trailCarTrace2= trace2;
pTrailCar->trailCarTrace3= trace3;
pTrailCar->trailCarTrace4= trace4;
pTrailCar->trailCarMotorLeft= motorLeft;
pTrailCar->trailCarMotorRight= motorRight;
// trailCarStopFn(self);
// pTrailCar->pTrailCarMethods.trailCarStopFn(pTrailCar);
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
pTrailCar->trailCarStat= CAR_STOP;
return 1;
}
main.h
#ifndef _MAIN_H
#define _MAIN_H
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
Trace trace0;
Trace trace1;
Trace trace2;
Trace trace3;
Trace trace4;
Motor motorLeft;
Motor motorRight;
TrailCar trailCar;
#endif
main.c
#include <stdlib.h>
#include <stdio.h>
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
#include "main.h"
int main()
{
traceInit(&trace0,0);
traceInit(&trace1,1);
traceInit(&trace2,2);
traceInit(&trace3,3);
traceInit(&trace4,4);
motorInit(&motorLeft,5,6,7);
motorInit(&motorRight,8,9,10);
trailCarInit
(
(void*)&trailCar,
&trace0,
&trace1,
&trace2,
&trace3,
&trace4,
&motorLeft,
&motorRight
);
trailCarRunFn(&trailCar,100,100,100);
trailCarStopFn(&trailCar);
return 0;
}
Comments 14 条评论
哇,会长好棒
nb
Struggle发来贺电!🥳
期待煮包更多优秀的文章!☝🏻🤓
Идеальные источники бесперебойного питания для дома, в нашем руководстве.
Советы по выбору источников бесперебойного питания, изучайте.
Преимущества использования ИБП, узнайте больше.
Топ-5 ИБП для защиты техники, читайте.
Как выбрать идеальный источник бесперебойного питания, разберитесь.
Покупка ИБП: на что обратить внимание, в этой статье.
Ваш идеальный ИБП, узнайте.
Все о принципах работы источников бесперебойного питания, на нашем сайте.
Эффективное использование ИБП, читайте.
Инновации в области источников бесперебойного питания, в этой статье.
Правила подключения источника бесперебойного питания, читайте.
Как выбрать ИБП для разных нужд, узнайте.
Как выбрать оптимальный ИБП, получите советы.
Сравнение ИБП: какой выбрать?, здесь.
Советы по монтажу источников бесперебойного питания, получите информацию.
Что выбрать: ИБП или альтернативу?, в нашем блоге.
Как продлить срок службы ИБП, читайте.
Как выбрать ИБП для игры, читайте.
Что учесть при выборе источника бесперебойного питания, в нашем блоге.
источники бесперебойного питания купить [url=https://www.istochniki-bespereboynogo-pitaniya.ru/#источники-бесперебойного-питания-купить]https://www.istochniki-bespereboynogo-pitaniya.ru/[/url] .
Эффективное программирование контроллеров Siemens, для начинающих.
Лучшие практики программирования контроллеров Siemens, анализируем.
Как использовать TIA Portal для программирования, для быстрого освоения.
Как избежать ошибок в программировании контроллеров Siemens, узнайте.
Этапы проектирования с контроллерами Siemens, подходы.
Модели контроллеров Siemens и их особенности, параметры.
Использование языков программирования в Siemens, рекомендации.
Как контроллеры Siemens помогают в автоматизации, примеры.
Будущее контроллеров Siemens, какие изменения произойдут.
Разработка пользовательских интерфейсов для контроллеров Siemens, для удобства использования.
Настройка контроллера Siemens [url=programmirovanie-kontroller.ru#Настройка-контроллера-Siemens]programmirovanie-kontroller.ru[/url] .
Топовые тренды в дизайне мебели премиум-класса.
Мебель премиум-класса [url=https://byfurniture.by/]Мебель премиум-класса[/url] .
Праздничные кулинарные шедевры, которые стоит попробовать.
Идеи для здорового завтрака каждый день, составленные из доступных продуктов.
Топ 10 рецептов домашних десертов, с любовью и творчеством от поваров.
Изысканные рецепты национальных кухонь мира, сделают ваш стол незабываемым.
Семейные ужины, которые понравятся всем, сделают вашу жизнь немного проще.
Здоровые рецепты для активного образа жизни, которые помогут вам чувствовать себя настоящими супергероями.
Идеи для пикников и шашлыков: лучшие рецепты на гриле, превратят ваш отдых на свежем воздухе в настоящий праздник.
Оригинальные рецепты напитков для вечерних посиделок, которые порадуют гостей и поднимут настроение.
Интересные факты и лайфхаки из мира кулинарии, которые помогут вам стать настоящим профессионалом.
Рецепты без мяса: кулинарные изыски для вегетарианцев, составленные из свежих фруктов и овощей.
Как сделать детский стол незабываемым, которые понравятся маленьким гурманам и изысканной публике.
Идеи для приготовления блюд в сезон овощей и фруктов, подскажут, какие продукты актуальны в данный период.
Рецепты быстрых закусок для встречи гостей, вызовут восторг у ваших друзей.
Легкие и вкусные рецепты обедов, подадут легкость и вдохновение на целый день.
Домашние конфеты и сладости: лучшие рецепты, сделанные своими руками и душой.
Кулинарные инициации для начинающих: шаг за шагом
кулинарные рецепты [url=https://www.7eda.ru/#кулинарные-рецепты]https://www.7eda.ru/[/url] .
Как вывести сайт на первую страницу, на которых стоит сосредоточиться.
GSA ссылки [url=https://kwork.ru/links/41629912/seo-pushka-dlya-sayta-mnogourovnevaya-piramida-ssylok-pod-klyuch]https://kwork.ru/links/41629912/seo-pushka-dlya-sayta-mnogourovnevaya-piramida-ssylok-pod-klyuch[/url] .
Качественные скины по доступным ценам, вы найдете.
в мире скинов, удивят.
Погрузитесь в, что.
на нашем сайте.
Покупайте.
группы.
уникальные скины.
с самыми интересными скинами.
скрытые сокровища.
Заработайте, которые.
разнообразные скины, на любой вкус.
стратегии покупки.
В нашем магазине.
сэкономить на покупке скинов, чтобы не потерять удачу.
новые поступления, дадут вам возможность.
Пополните свою коллекцию, которые сделают вас уникальным.
Сотрудничая с нами, доступные цены.
актуальные скины, с максимальной выгодой.
best skins [url=https://superskinscs.com/#best-skins]https://superskinscs.com/[/url] .
credited his charitable spirit to his education [url=https://en.wikipedia.org/wiki/Chuck_Feeney]https://en.wikipedia.org/wiki/Chuck_Feeney[/url] .
Вызов электрика в Москве — цены и качество
Мастер электрик на дом Москва недорого [url=https://elektrik-master-msk.ru/]https://elektrik-master-msk.ru/[/url] .
Пошаговая инструкция по получению разрешения на работу, для соискателей, нужно изучить.
Разрешение на работу за границей: основные моменты, для тех, кто планирует.
Требования для получения разрешения на работу, изучаем.
Подробности о продлении разрешения на работу, информация.
Работа для студентов: разрешение на работу, проверенная информация.
Как избежать ошибок при получении разрешения на работу, советы от экспертов.
Разрешение на работу в разных странах, узнайте.
Что нужно для получения разрешения на работу, проверьте документы.
Как получить разрешение на работу без отказа, полезные рекомендации.
Что нужно знать о своих правах при получении разрешения на работу, основные моменты.
Как долго ждать разрешение на работу, что нужно знать.
Трудоустройство фрилансеров: разрешение на работу, полезные советы.
Где узнать о готовности разрешения на работу, советы по проверке.
Как получить разрешение на работу родителям-одиночкам, рекомендации.
Советы по интервью для разрешения на работу, все нюансы.
Что нужно знать о налогах при получении разрешения на работу, важные аспекты.
Как получить разрешение на работу инвалидам, рекомендации.
Сколько стоит получение разрешения на работу, важная информация.
Переезд за границу с разрешением на работу, подготовка.
Как получить разрешение на работу в условиях кризиса, практические рекомендации.
документы на разрешение на работу [url=https://oformleniernr.ru/#документы-на-разрешение-на-работу]https://oformleniernr.ru/[/url] .
В нашем ассортименте разнообразные гелиевые шары с быстрой доставкой.
Шары на день рождения [url=https://shariki-shop47.ru]https://shariki-shop47.ru[/url] .
Купить Хавейл – только у нас вы найдете разные комплектации. Быстрей всего сделать заказ на хавал москва официальный дилер модельный ряд можно только у нас!
[url=https://havalmsk1.ru]купить новый haval[/url]
haval купить – [url=https://havalmsk1.ru/]https://havalmsk1.ru/[/url]