01
مشغل خط الأنابيب02
تنفيذ حرفي مخصص _f
03
تنفيذ print
والتخصص std::formatter
04
قم بتعديل قالب الفصل المحدد بحيث يحتوي على معرف مختلف لكل نوع مختلف من إنشاء مثيل05
نوع scope_guard
06
شرح std::atomic
07
throw new MyException
08
تعريف دليل اشتقاق array
09
10
اجتياز أي أعضاء بيانات الفصلC++17
C++20
11
مشكلة في emplace_back()
12
make_vector()
13
return std::move
14
تعديل الكائنات المعلنة في مساحة الاسم بطرق خاصة15
قوالب التعبير16
وحدة ماكرو لصنع قوالب وظائف النقلعرض واجبات لوثر المنزلية.
لا ينبغي أن يؤدي إرسال العلاقات العامة إلى تغيير الملف README
الحالي. يرجى إرسال المهمة إلى src群友提交
. على سبيل المثال، إذا كنت تريد إرسال الوظيفة الأولى:
يجب عليك إنشاء ملف .md
أو .cpp
الخاص بك في src群友提交第01题
يجب تسمية اسم الملف بعد معرف مجموعة الاتصال الخاصة بك (أو اسم مستخدم GitHub، حتى تتمكن من العثور على نفسك بسهولة) .
المتطلبات العامة للإجابة على الأسئلة هي كما يلي (الانتباه إلى المتطلبات الإضافية للأسئلة):
main
، فيجب عدم إيقاف تشغيلها (أي عدم الاستفادة منها).01
مشغل خط الأنابيب التاريخ: 2023/7/21
السائل: mq白
نظرا للكود:
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
1 4 9
تم الرد بواسطة : andyli
# include < algorithm >
# include < vector >
# include < functional >
# include < iostream >
template < typename R, typename F>
auto operator |(R&& r, F&& f) {
for ( auto && x: r)
f (x);
return r;
}
int main () {
std::vector v{ 1 , 2 , 3 };
std::function f{[]( const int & i) { std::cout << i << ' ' ; }};
auto f2 = []( int & i) { i *= i; };
v | f2 | f;
}
هذا طبيعي، لا مشكلة.
تم الرد عليه بواسطة: mq松鼠
# include < iostream >
# include < vector >
# include < functional >
auto operator | (std::vector< int >&& v,std::function< void ( const int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
auto operator | (std::vector< int >& v,std::function< void ( int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' n ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
تعليق: إذا لم يكن لدي ما أفعله، سأكتب المزيد من الأحمال الزائدة وأقوم بتأطيرها.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //可加可不加,不会就不加
std::vector<U>& operator |(std::vector<U>& v1, F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
بدون استخدام القوالب :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
بدلاً من استخدام نطاق for
، استخدم قالب الدالة المختصرة C++ 20:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
نماذج الإجابات المختلفة الأخرى ليست أكثر من هذه التغييرات، ليست هناك حاجة لكتابتها مرة أخرى.
من الواضح أننا بحاجة إلى زيادة التحميل على مشغل خط الأنابيب |. وفقًا لنموذج الاستدعاء v | f2 | f
، فإن سلسلة الاستدعاءات هذه، واستنادًا إلى نتائج التشغيل المحددة، يمكننا أن نعرف أن الدالة المحملة بشكل زائد يجب أن تُرجع مرجعًا إلى v وv. سيتم تعديلها. v | f2
operator |
. يستخدم f2 لاجتياز كل عنصر في v، ثم يُرجع مرجع v، ثم |.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
إذا لم تتعرض أبدًا لتعبيرات القيد، فلا يهم، فسنقدمها بإيجاز أدناه.
التعبير يتطلب يشبه دالة تُرجع منطقيًا، ويتم ملء U وF في قائمة المعلمات الفعلية لـ std::regular_invocable كأنواع طالما أن النوعين U وF يستوفيان التعبير، فإنه يُرجع صحيحًا إذا لم يكن كذلك ، فإنه يُرجع خطأ، وهو ما يسمى "القيد غير راضٍ". الأنواع التي لا تستوفي القيود بطبيعة الحال لن تنفذ التعليمات البرمجية اللاحقة.
أما بالنسبة لـ std::regular_invocable، فيمكننا ببساطة التفكير فيها كزوج من كل قيمة من النوع U. وما إذا كان بإمكاننا استدعاء الدالة F، أي استدعاء std::invoke
.
وهذا يعادل تخيل وقت التشغيل في وقت الترجمة وتخيل ما إذا كان بإمكان U تنفيذ F في وقت التشغيل. إذا كان الأمر كذلك، فإنه يفي بالقيود.
الجسم الوظيفي بسيط للغاية
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
تعبير النطاق for (auto& i : v1)
يشبه for(auto i=v.begin();i!=v.end();++i){f(*i)}
: لدينا متجه (range ) يطبق الدالة f مرة واحدة على كل عنصر فيها. يعود إلى الإصدار 1 كالمعتاد.
إذا لم نستخدم القوالب، فيجب أن تستخدم قائمة المعلمات الرسمية لدينا std::function لالتقاط الوظيفة التي نستخدمها:
لا يتطلب تطبيق f على كل عضو في النطاق قيمة إرجاع ويتطلب تعديل العناصر الموجودة في النطاق، لذا فإن المعلمة الثانية هي std::function<void(int&)>
. ولا نحتاج إلى تعديل أو نسخ الدالة f التي تم تمريرها، لذا من الجيد إضافة قيود const .
وبالمثل، لا يمكننا استخدام range ولكن الأبسط std::ranges::for_each(v1, f);
أي تطبيق الدالة f مرة واحدة على كل عنصر في النطاق v1 كما هو مذكور أعلاه.
بالنسبة لشكل استخدام القوالب، يمكننا استخدام قالب دالة الاختصار C++20؛ باختصار، سيقوم العنصر النائب التلقائي في قائمة معلمات الوظيفة بإلحاق معلمة قالب وهمية بقائمة معلمات القالب. يمكن كتابة نموذج القالب الأولي كـ
std::vector< int >& operator |( auto & v1, const auto & f)
وهو نفس الشكل الأصلي.
02
تنفيذ حرفي مخصص _f
التاريخ: 2023/7/22
السائل: mq白
نظرا للكود:
int main (){
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
乐 :5 *
乐 :5 5 *
乐 :1010101 *
卢瑟******
6
π:3.141593
6
هو الإدخال ويحدد
تم الرد بواسطة : andyli
# include < format >
# include < iostream >
# include < string_view >
# include < string >
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
int main () {
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
constexpr auto operator " " _f( const char * fmt, size_t ) {
return [=]< typename ... T>(T&&... Args) { return std::vformat (fmt, std::make_format_args (Args...)); };
}
نحن بحاجة إلى استخدام C++ 11 حرفية محددة من قبل المستخدم، و ""_f
هو بالضبط الحرفي المحدد من قبل المستخدم.
ومع ذلك، فإن قائمة المعلمات الرسمية للعامل الحرفي (الوظيفة التي يطلق عليها العامل الحرفي المحدد بواسطة المستخدم تسمى العامل الحرفي) لها بعض القيود. ما نحتاج إليه هو قائمة معلمات رسمية مثل const char *, std::size_t
, والتي يحدث أن هذا مسموح به؛ يحتاج نوع الإرجاع للعامل الحرفي إلى التخصيص، ويحتاج هذا النوع إلى التحميل الزائد operator()
داخليًا لتلبية المتطلبات المذكورة أعلاه لكي يتم استدعاء القيم الحرفية مثل الوظائف.
دعنا نذهب خطوة بخطوة:
void operator " " _test( const char * str, std:: size_t ){
std::cout << str << ' n ' ;
}
" luse " _test; //调用了字面量运算符,打印 luse
std:: size_t operator " " _test( const char * , std:: size_t len){
return len;
}
std:: size_t len = " luse " _test; //调用了字面量运算符,返回 luse 的长度 4
يوضح مثالا الاستخدام للكود أعلاه الاستخدام الأساسي للقيم الحرفية المحددة من قبل المستخدم، مع إيلاء اهتمام خاص للفقرة الثانية، قيمة الإرجاع . إذا كنت تريد تسميتها مثل "xxx"_f(xxx)
، فعليك أن تفعل شيئًا بنوع الإرجاع.
struct X {
std:: size_t operator ()(std:: size_t n) const {
return n;
}
};
X operator " " _test( const char * , std:: size_t ){
return {};
}
std::cout<< "无意义" _test( 1 ); //打印 1
الكود البسيط أعلاه يكمل نموذج الاستدعاء الذي نحتاجه بشكل مثالي، ثم حان الوقت لإكمال الوظائف التي يتطلبها السؤال. إن أبسط طريقة هي استخدام مكتبة التنسيق C++20 مباشرة للتنسيق.
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
operator""_f
بحد ذاته بسيط جدًا، فهو يستخدم فقط لإنشاء كائن impl::Helper
من المعلمات الواردة (سلسلة التنسيق) والطول ثم إعادته. يستخدم النوع Helper
string_view
كعضو بيانات لتخزين سلسلة التنسيق لتنسيقها لاحقًا.
ينصب التركيز فقط على operator()
. إنه قالب معلمات متغير، يُستخدم لتلقي أي نوع وعدد من المعلمات التي نمررها، ثم يُرجع سلسلة منسقة.
ما يتم استخدامه هنا هو std::vformat
للتنسيق. المعلمة الأولى هي سلسلة التنسيق، أي القواعد التي نريد تنسيقها وفقًا لها؛ قم بتوسيع حزمة المعلمة الرسمية، ونوع المعلمة الثانية هو في الواقع std::format_args
. يجب علينا استخدام الدالة std::make_format_args
لتمرير المعلمات الخاصة بنا، وستعيد النوع std::format_args
في الواقع، وهو ما يعادل التحويل، وهو أمر معقول.
لكن من الواضح أن الإجابة القياسية ليست هكذا، ويمكن تبسيطها ببساطة عن طريق السماح ""_f
بإرجاع تعبير لامدا.
03
تنفيذ print
والتخصص std::formatter
التاريخ: 2023/7/24
السائل: mq白
تنفيذ print
، إذا قمت بالمهمة السابقة، أعتقد أن هذا أمر بسيط. نموذج الاستدعاء المطلوب هو :
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
نظرًا لنوع Frace
المخصص، اطلب الدعم
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
البرمجة الموجهة نحو النتائج، واستخدام وحدات الماكرو، وما إلى ذلك محظورة، بحد أقصى B
(في إشارة إلى التقييم). تفحص هذه المهمة بشكل أساسي مكتبة format
وتتعلمها.
نصيحة: std::formatter
من الأفضل إرسال الكود مع لقطات شاشة لثلاث منصات تم تجميعها عبر الإنترنت، مثل:
template <>
struct std ::formatter<Frac>:std::formatter< char >{
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
نحن ببساطة ندعم النموذج الذي يتطلبه السؤال ونتخصص std::formatter
إذا أردنا دعم التنسيق مثل {:6}
فمن الواضح أن هذا يتطلب المزيد من العمليات. يمكن العثور على التخصصات والنماذج البسيطة التي يدعمها std::formatter
في الوثائق . تمت كتابة بعض التخصصات المعقدة من قبل في كتاب الطبخ ، هناك تخصصات لـ std::ranges::range
و std::tuple
، تدعم جميع الأشكال.
يعد تنفيذ الطباعة أمرًا بسيطًا للغاية، نحتاج فقط إلى اتباع فكرة السؤال الثاني، استخدم std::string_view كمعلمة رسمية أولى، بالإضافة إلى ذلك، إذا كانت هناك حاجة إلى أي معلمات وأرقام حزمة المعلمة الرسمية
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
يؤدي استدعاء vformat
بهذه الطريقة إلى إرجاع سلسلة يمكن إخراجها مباشرةً باستخدام cout.
فيما يتعلق بتخصص std::formatter
المخصص، ما نحتاج إلى معرفته هو: إذا كنت تريد تخصيص تخصص قالب std::formatter ، فأنت بحاجة إلى توفير وظيفتين، التحليل والتنسيق .
يتم استخدام التحليل لمعالجة أوصاف التنسيق وتعيين متغيرات الأعضاء ذات الصلة. بالنسبة لهذا السؤال، لا نحتاج إلى تحمل مشكلة تنفيذ وظيفة العضو هذه؛
نختار أن نرث وظيفة التحليل لـ std::formatter<char>
وننفذ وظيفة التنسيق بشكل مستقل. إذا لم تفهم صياغة تخصصات القالب هنا، فراجع تخصصات القالب.
template <>
struct std ::formatter<Frac> : std::formatter< char > {
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
نستخدم أيضًا auto كقالب دالة مختصرة للعناصر النائبة بالنسبة لوظيفة التنسيق ، المعلمة الأولى هي الفئة المخصصة التي نمررها، والمعلمة الثانية ( ctx ) هي حرف التنسيق الذي نريد تمريره إلى مكرر الإخراج std::format_to
. خيط.
في نص الوظيفة، نعيد مباشرة نتيجة تعبير الاستدعاء std::format_to()
هذه الوظيفة ترجع مكرر الإخراج للقيمة المرجعة، نستخدم العنصر النائب التلقائي لاشتقاق القيمة المرجعة.
من بين معلمات الوظيفة، ctx.out()
هو مكرر الإخراج، والمعلمة الثانية هي سلسلة تنسيق قانونية يمكن تحويلها إلى std::string_view
أو std::wstring_view
، ونتيجة التحويل هي تعبير ثابت وArgs. في هذا السؤال نقوم بملء النموذج الذي نحتاجه وهو {}/{}
.
نريد حشو المعلمتين في {}
، تمامًا كما نستخدم printf(%d,x)
؛ المعلمتان الأخيرتان هما "القيم التي يجب حشوها في {}
"، أي المعلمات تكون مهيأة.
04
قم بتعديل قالب الفصل المحدد بحيث يحتوي على معرف مختلف لكل نوع مختلف من إنشاء مثيل التاريخ: 2023/7/25
السائل: أدتيل
# include < iostream >
class ComponentBase {
protected:
static inline std:: size_t component_type_count = 0 ;
};
template < typename T>
class Component : public ComponentBase {
public:
// todo...
//使用任意方式更改当前模板类,使得对于任意类型X,若其继承自Component
//则X::component_type_id()会得到一个独一无二的size_t类型的id(对于不同的X类型返回的值应不同)
//要求:不能使用std::type_info(禁用typeid关键字),所有id从0开始连续。
};
class A : public Component <A>
{};
class B : public Component <B>
{};
class C : public Component <C>
{};
int main ()
{
std::cout << A::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << C::component_type_id () << std::endl;
}
0
1
1
0
0
2
يجب أن يقدم التقديم نتائج اختبار متعدد المنصات، كما هو موضح في الشكل:
template < typename T>
class Component : public ComponentBase {
public:
static std:: size_t component_type_id (){
static std:: size_t ID = component_type_count++;
return ID;
}
};
تحليل:
نحن بحاجة إلى تنفيذ وظيفة العضو الثابت component_type_id
الخاصة Component
. وهذا معروف من الكود المعطى:
class A : public Component <A>
{};
A::component_type_id ()
يتطلب السؤال أن كل نوع فئة مخصص (من المفترض أن يكون X) يرث Component<X>
، ويؤدي استدعاء component_type_id()
إلى إرجاع المعرف الفريد الخاص به. الشيء نفسه ينطبق على الأنواع الأخرى.
قبل حل المشكلة، نحتاج إلى التأكيد على نقطة معرفة واحدة:
قوالب C++ ليست أنواعًا محددة، فهي بعد إنشاء مثيل (أي أن قوالب الوظائف ليست وظائف، وقوالب الفئات ليست فئات ). لا ينتمي الأعضاء الثابتون أو وظائف الأعضاء الثابتة في قوالب الفئة إلى قوالب، بل إلى أنواع محددة بعد إنشاء مثيل يمكننا استخدام جزء من التعليمات البرمجية لتوضيح الاستنتاج:
# include < iostream >
template < typename T>
struct Test {
inline static int n = 10 ;
};
int main (){
Test< int >::n = 1 ;
std::cout << Test< void >::n << ' n ' ; // 10
std::cout << Test< int >::n << ' n ' ; // 1
}
يوضح هذا الرمز بسهولة أن أعضاء البيانات الثابتة ينتمون إلى نوع معين بعد إنشاء مثيل للقالب . Test<void>::n
و Test<int>::n
ليسا نفس n، و Test<void>
و Test<int>
ليسا من نفس النوع (وينطبق الشيء نفسه على وظائف الأعضاء الثابتة).
لذلك يستخدم الحل الخاص بنا: الأنواع المختلفة من قوالب فئة Component
التي تم إنشاء مثيل لها هي أيضًا وظائف أعضاء ثابتة مختلفة، كما أن الأجزاء الثابتة في وظائف الأعضاء الثابتة فريدة أيضًا ولن تتم تهيئتها إلا عند استدعائها لأول مرة.
05
نوع scope_guard
التاريخ: 2023/7/29
السائل: داينيهلس
مطلوب تنفيذ نوع scope_guard
(أي أنه يدعم تمرير أي نوع قابل للاستدعاء واستدعاءه في نفس الوقت أثناء التدمير).
# include < cstdio >
# include < cassert >
# include < stdexcept >
# include < iostream >
# include < functional >
struct X {
X () { puts ( " X() " ); }
X ( const X&) { puts ( " X(const X&) " ); }
X (X&&) noexcept { puts ( " X(X&&) " ); }
~X () { puts ( " ~X() " ); }
};
int main () {
{
// scope_guard的作用之一,是让各种C风格指针接口作为局部变量时也能得到RAII支持
// 这也是本题的基础要求
FILE * fp = nullptr ;
try {
fp = fopen ( " test.txt " , " a " );
auto guard = scope_guard ([&] {
fclose (fp);
fp = nullptr ;
});
throw std::runtime_error{ " Test " };
} catch (std:: exception & e){
puts (e. what ());
}
assert (fp == nullptr );
}
puts ( " ---------- " );
{
// 附加要求1,支持函数对象调用
struct Test {
void operator ()(X* x) {
delete x;
}
} t;
auto x = new X{};
auto guard = scope_guard (t, x);
}
puts ( " ---------- " );
{
// 附加要求2,支持成员函数和std::ref
auto x = new X{};
{
struct Test {
void f (X*& px) {
delete px;
px = nullptr ;
}
} t;
auto guard = scope_guard{&Test::f, &t, std::ref (x)};
}
assert (x == nullptr );
}
}
Test
----------
X()
~X()
----------
X()
~X()
std::function
وامسح النوع struct scope_guard {
std::function< void ()>f;
template < typename Func, typename ...Args> requires std::invocable<Func, std:: unwrap_reference_t <Args>...>
scope_guard (Func&& func, Args&&...args) :f{ [func = std::forward<Func>(func), ... args = std::forward<Args>(args)]() mutable {
std::invoke (std::forward<std:: decay_t <Func>>(func), std:: unwrap_reference_t <Args>(std::forward<Args>(args))...);
} }{}
~scope_guard () { f (); }
scope_guard ( const scope_guard&) = delete ;
scope_guard& operator =( const scope_guard&) = delete ;
};
std::tuple
+ std::apply
template < typename F, typename ...Args>
requires requires (F f, Args...args) { std::invoke (f, args...); }
struct scope_guard {
F f;
std::tuple<Args...>values;
template < typename Fn, typename ...Ts>
scope_guard (Fn&& func, Ts&&...args) :f{ std::forward<Fn>(func) }, values{ std::forward<Ts>(args)... } {}
~scope_guard () {
std::apply (f, values);
}
scope_guard ( const scope_guard&) = delete ;
};
template < typename F, typename ...Args> //推导指引非常重要
scope_guard (F&&, Args&&...) -> scope_guard<std::decay_t<F>, std::decay_t<Args>...>;
06
شرح std::atomic
التاريخ: 2023/8/2
السائل: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
اشرح لماذا يمكن تجميع الكود أعلاه بعد C++ 17 ولكن ليس قبل C++ 17؟
في std::atomic<int> n = 6
، نظرًا لأن 6
و std::atomic<int>
ليسا من نفس النوع (ولكن يوجد في الواقع تسلسل تحويل محدد من قبل المستخدم هنا، يمكنك ببساطة التفكير في أنه يمكن تحويل 6
ضمنيًا ).
وهذا يعني استدعاء مُنشئ التحويل:
constexpr atomic ( T desired ) noexcept ;
تُستخدم مُنشئات التحويل أيضًا كجزء من تسلسل التحويل المحدد من قبل المستخدم
6
سوف يستدعي مُنشئ التحويل لإنشاء كائن ذري مؤقت لتهيئة n
مباشرةً ، أي
std::atomic< int > n (std::atomic< int >( 6 ))
في الإصدارات قبل C++17 ، من الطبيعي أن يتم البحث عن مُنشئ النسخ/النقل واكتشافه قبل أن يتم تجميعه إذا كان يلبي المتطلبات. لكن: