কেন স্টাড :: গেটলাইন () ফর্ম্যাট নিষ্কাশন পরে ইনপুট ছেড়ে যায়?


105

আমার কাছে নিম্নলিখিত কোডের টুকরা রয়েছে যা ব্যবহারকারীকে তাদের নাম এবং রাজ্যের জন্য অনুরোধ করে:

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::cin >> name && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
}

আমি যা খুঁজে পাই তা হল নামটি সাফল্যের সাথে তোলা হয়েছে, তবে রাষ্ট্রটি নয়। এখানে ইনপুট এবং ফলাফল আউটপুট:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

রাজ্যের নাম আউটপুট থেকে বাদ দেওয়া হয়েছে কেন? আমি সঠিক ইনপুট দিয়েছি, কিন্তু কোডটি কোনওভাবে এটিকে উপেক্ষা করে। কেন এমন হয়?


আমি বিশ্বাস std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)করিও প্রত্যাশার মতো কাজ করা উচিত। (নীচের উত্তরগুলি ছাড়াও)।
jww

উত্তর:


122

কেন এমন হয়?

আপনার নিজের সরবরাহ করা ইনপুটটির সাথে এটির সামান্যই নয় বরং ডিফল্ট আচরণের std::getline()প্রদর্শনগুলির সাথে। নাম ( std::cin >> name) এর জন্য আপনি যখন আপনার ইনপুট সরবরাহ করেছিলেন , আপনি কেবল নিম্নলিখিত অক্ষরগুলি জমা দিয়েছিলেন তা নয়, তবে প্রবাহে একটি অন্তর্নিহিত নিউলাইনও যুক্ত হয়েছিল:

"John\n"

আপনি যখন টার্মিনালটি নির্বাচন করেন Enterবা Returnজমা দেওয়ার সময় একটি নতুন লাইন সর্বদা আপনার ইনপুটটিতে যুক্ত হয় । এটি পরের লাইনের দিকে যাওয়ার জন্য ফাইলগুলিতেও ব্যবহৃত হয়। নতুন লাইনটি nameপরবর্তী আই / ও অপারেশন পর্যন্ত নিষ্কাশনের পরে বাফারে রেখে দেওয়া হয় যেখানে এটি হয় ফেলে দেওয়া বা গ্রাস করা হয়। নিয়ন্ত্রণের প্রবাহ পৌঁছে std::getline()গেলে, নতুন লাইনটি ফেলে দেওয়া হবে, তবে ইনপুটটি অবিলম্বে বন্ধ হয়ে যাবে। এটি হওয়ার কারণ হ'ল কারণ এই ফাংশনের ডিফল্ট কার্যকারিতা হ'ল এটি করা উচিত (এটি একটি লাইন পড়ার চেষ্টা করে এবং যখন এটি একটি নতুন লাইন খুঁজে পায় তখন থামবে)।

যেহেতু এই শীর্ষস্থানীয় নিউলাইনটি আপনার প্রোগ্রামের প্রত্যাশিত কার্যকারিতা বাধা দেয়, এটি অনুসরণ করে যে এটি অবশ্যই আমাদের উপেক্ষা করা কোনওভাবে ছাড়তে হবে। একটি বিকল্প std::cin.ignore()প্রথম নিষ্কাশন পরে কল করা হয়। এটি পরবর্তী উপলব্ধ অক্ষরটিকে ফেলে দেবে যাতে নিউলাইন আর পথে না থাকে।

std::getline(std::cin.ignore(), state)

গভীরতার ব্যাখ্যা:

এটি std::getline()আপনি যে কল করেছেন তার ওভারলোড :

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

এই ফাংশনের আর একটি ওভারলোড প্রকারের একটি সীমানা গ্রহণ করে charT। একটি ডিলিমিটার অক্ষর এমন একটি অক্ষর যা ইনপুটের ক্রমগুলির মধ্যে সীমানা উপস্থাপন করে। এই সরবরাহ করা হয়নি বলে এই বিশেষ ওভারলোডটি input.widen('\n')ডিফল্ট হিসাবে নতুন লাইন চরিত্রে ডিলিমিটার সেট করে ।

এখন, এগুলি কয়েকটি শর্ত যার মাধ্যমে std::getline()ইনপুটটি সমাপ্ত করে:

  • যদি স্ট্রিমটি std::basic_string<charT>ধারণ করতে পারে এমন সর্বাধিক পরিমাণে অক্ষর বের করে
  • যদি ফাইলের শেষের (ইওএফ) অক্ষরটি খুঁজে পাওয়া যায়
  • যদি ডিলিমিটারটি পাওয়া যায়

তৃতীয় শর্তটি হ'ল আমরা যার সাথে আচরণ করছি। আপনার ইনপুটটি stateএভাবে উপস্থাপিত হয়:

"John\nNew Hampshire"
     ^
     |
 next_pointer

next_pointerপরের অক্ষরটি কোথায় বিশ্লেষণ করা হবে। যেহেতু ইনপুট অনুক্রমের পরবর্তী অবস্থানে সঞ্চিত অক্ষরটি ডিলিমিটার তাই std::getline()চুপচাপ সেই অক্ষরটি বাতিল করে next_pointerদেবে, পরবর্তী উপলব্ধ অক্ষরের বৃদ্ধি হবে এবং ইনপুট বন্ধ করবে। এর অর্থ হ'ল আপনি যে অক্ষরগুলি সরবরাহ করেছেন সেগুলি পরবর্তী আই / ও ক্রিয়াকলাপের জন্য এখনও বাফারে রয়ে গেছে। আপনি লক্ষ্য করবেন যে আপনি যদি লাইন থেকে অন্যটি পড়া শুরু করেন তবে stateআপনার নিষ্কাশনটি std::getline()ডিলিমিটারকে বাতিল করার শেষ কল হিসাবে সঠিক ফলাফল দেবে ।


আপনি লক্ষ্য করেছেন যে ফর্ম্যাট ইনপুট অপারেটর ( operator>>()) দিয়ে এক্সট্রাক্ট করার সময় আপনি সাধারণত এই সমস্যাটির মধ্যে চলে না । এর কারণ ইনপুট স্ট্রিমগুলি ইনপুটটির জন্য ডেলিমেটার হিসাবে সাদা স্থান ব্যবহার করে এবং std::skipws1 টি ম্যানিপুলেটর ডিফল্টরূপে সেট করে। ফর্ম্যাট করা ইনপুট করা শুরু করার সময় স্ট্রিমগুলি স্ট্রিম থেকে শীর্ষস্থানীয় সাদা স্থানকে বাতিল করবে। 2

ফরম্যাট করা ইনপুট অপারেটার মতো std::getline()একটি হল অবিন্যস্ত ইনপুট ফাংশন। এবং সমস্ত অপরঠিত ইনপুট ফাংশনগুলির নিম্নলিখিত কোডটি কিছুটা সাধারণ থাকে:

typename std::basic_istream<charT>::sentry ok(istream_object, true);

উপরেরটি একটি প্রেরিত অবজেক্ট যা মানক সি ++ বাস্তবায়নে সমস্ত ফর্ম্যাট / অপরিবর্তিত আই / ও ফাংশনগুলিতে ইনস্ট্যান্ট হয়। I / O এর জন্য স্ট্রিম প্রস্তুত করতে এবং এটি ব্যর্থ অবস্থায় রয়েছে কিনা তা নির্ধারণের জন্য সেনট্রি অবজেক্টগুলি ব্যবহৃত হয়। আপনি কেবল এটি অক্ষম ইনপুট ফাংশনগুলিতে খুঁজে পাবেন , সেন্ড্রি কনস্ট্রাক্টরের দ্বিতীয় যুক্তি true। এই যুক্তিটির অর্থ হ'ল নেতৃস্থানীয় হোয়াইটস্পেসটি ইনপুট ক্রমের শুরু থেকে বাদ দেওয়া হবে না । মানক [from27.7.2.1.3 / 2] এর প্রাসঙ্গিক উদ্ধৃতিটি এখানে:

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] যদি noskipwsশূন্য হয় এবং is.flags() & ios_base::skipwsননজারো হয় তবে ফাংশনটি প্রতিটি অক্ষরকে বের করে দেয় এবং ততক্ষণ ততক্ষণ পাওয়া যায় যতক্ষণ না পরবর্তী উপলভ্য ইনপুট অক্ষর cএকটি সাদা স্থানের অক্ষর হয়। [...]

যেহেতু উপরের শর্তটি মিথ্যা, প্রেরিত বস্তু হোয়াইট স্পেসটি ফেলে দেবে না। কারণটি এই ফাংশনটি দ্বারা noskipwsসেট করা হয়েছে trueকারণ বিন্দুটি std::getline()হ'ল কোনও std::basic_string<charT>বস্তুতে কাঁচা, অবরুদ্ধ অক্ষরগুলি পড়া ।


সমাধান:

এর আচরণ বন্ধ করার কোনও উপায় নেই std::getline()। আপনাকে যা করতে হবে তা হ'ল std::getline()রান করার আগে নতুন লাইনটি নিজেকে ফেলে দিন (তবে ফর্ম্যাট নিষ্কাশনের পরে এটি করুন)। ignore()আমরা কোনও তাজা নতুন লাইনে না পৌঁছানো পর্যন্ত বাকি ইনপুটটি ফেলে দেওয়ার জন্য এটি ব্যবহার করে করা যেতে পারে :

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

<limits>আপনাকে ব্যবহারের জন্য অন্তর্ভুক্ত করতে হবে std::numeric_limitsstd::basic_istream<...>::ignore()এটি এমন একটি ফাংশন যা নির্দিষ্ট পরিমাণের অক্ষরগুলি ছাড়িয়ে দেয় যতক্ষণ না এটি হয় সীমানা সন্ধান করে বা প্রবাহের শেষ প্রান্তে না পৌঁছায় ( ignore()এটি যদি সন্ধান করে তবে ডিলিমিটারটিও বাদ দেয়)। max()ফাংশন অক্ষর আছে যা একটি স্ট্রিম গ্রহণ করতে পারে সর্ববৃহৎ পরিমাণ ফেরৎ।

হোয়াইটস্পেস ফেলে দেওয়ার আরেকটি উপায় হ'ল std::wsফাংশনটি ব্যবহার করা যা কোনও ইনপুট স্ট্রিমের শুরু থেকে নেতৃস্থানীয় হোয়াইটস্পেস বের করতে এবং বাতিল করতে ডিজাইন করা ম্যানিপুলেটর:

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

পার্থক্য কি?

পার্থক্যটি হ'ল ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 টি নির্বিচারে অক্ষরগুলি বিচ্ছিন্ন করে না দেয় যতক্ষণ না এটি countচরিত্রগুলি বিযুক্ত করে , ডিলিমিটারটি খুঁজে পায় (দ্বিতীয় তর্ক দ্বারা সুনির্দিষ্ট delim) বা প্রবাহের শেষদিকে আঘাত করে না। std::wsকেবলমাত্র স্ট্রিমের শুরু থেকে সাদা অংশের অক্ষরগুলি ছড়িয়ে দেওয়ার জন্য ব্যবহৃত হয়।

যদি আপনি ফর্ম্যাট করা ইনপুটটি অপরঠিত ইনপুটটির সাথে মিশ্রিত করে থাকেন এবং আপনার অবশিষ্ট শ্বেত স্পেস বাতিল করতে হবে, ব্যবহার করুন std::ws। অন্যথায়, আপনার যদি অবৈধ ইনপুটটি নির্বিশেষে পরিষ্কার করতে হয় তবে তা ব্যবহার করুন ignore()। আমাদের উদাহরণস্বরূপ, কেবলমাত্র স্ট্রিমটি আপনার পরিবর্তনশীলটির "John"জন্য ইনপুট গ্রাস করায় আমাদের কেবল সাদা স্থান পরিষ্কার করতে হবে name। যা যা ছিল তা হ'ল নিউলাইন চরিত্র।


1: std::skipwsম্যানিপুলেটর যা ফর্ম্যাট ইনপুট সম্পাদন করার সময় ইনপুট স্ট্রিমকে নেতৃস্থানীয় হোয়াইটস্পেস ত্যাগ করতে বলে। std::noskipwsম্যানিপুলেটর দিয়ে এটি বন্ধ করা যেতে পারে ।

2: ইনপুট স্ট্রিমগুলি নির্দিষ্ট অক্ষরকে ডিফল্টরূপে হোয়াইটস্পেস হিসাবে মনে করে, যেমন স্থানের অক্ষর, নিউলাইন চরিত্র, ফর্ম ফিড, ক্যারেজ রিটার্ন ইত্যাদি characters

3: এটি এর স্বাক্ষর std::basic_istream<...>::ignore()। আপনি স্ট্রিম থেকে একটি একক অক্ষর বাতিল করতে শূন্য আর্গুমেন্ট সহ এটি বলতে পারবেন, একটি নির্দিষ্ট পরিমাণ অক্ষর ফেলে দেওয়ার জন্য একটি যুক্তি, বা countঅক্ষরগুলি বাতিল করতে দুটি আর্গুমেন্ট বা এটি পৌঁছানো পর্যন্ত delim, যেটি প্রথমে আসে। আপনি ডিলিমিটারের আগে কয়টি অক্ষর রয়েছে তা যদি আপনি না জানেন তবে আপনি সাধারণত এটির std::numeric_limits<std::streamsize>::max()মান হিসাবে ব্যবহার করেন countতবে আপনি সেগুলি যেভাবেই ফেলে দিতে চান।


1
কেন সহজভাবে নয় if (getline(std::cin, name) && getline(std::cin, state))?
ফ্রেড লারসন

@ ফ্রেডলারসন ভালো পয়েন্ট যদিও প্রথম নিষ্কাশনটি কোনও পূর্ণসংখ্যার বা স্ট্রিং নয় এমন কোনও কিছুতে কাজ করে না।
0x499602D2

অবশ্যই এটি এখানে নেই এবং একই জিনিস দুটি ভিন্ন উপায়ে করার কোনও মানে নেই point একটি পূর্ণসংখ্যার জন্য আপনি একটি স্ট্রিংয়ের মধ্যে লাইনটি পেতে পারেন এবং তারপরে ব্যবহার করতে পারেন std::stoi(), তবে তারপরে এটির কোনও সুবিধা নেই তা পরিষ্কার clear তবে আমি কেবল std::getline()লাইন-ভিত্তিক ইনপুট জন্য ব্যবহার করতে পছন্দ করি এবং তারপরে লাইনটি যেভাবেই বোঝায় সেটিকে পার্সিংয়ের সাথে ডিল করি। আমি মনে করি এটির ত্রুটি কম রয়েছে।
ফ্রেড লারসন

@ ফ্রেডলারসন সম্মত হয়েছেন। আমার যদি সময় থাকে তবে আমি এটি যুক্ত করব।
0x499602D2

1
@ অ্যালবিন আপনি যে কারণটি ব্যবহার করতে চান তা std::getline()হ'ল যদি আপনি কোনও নির্দিষ্ট ডিলিমিটার পর্যন্ত সমস্ত অক্ষর ক্যাপচার করতে চান এবং এটি একটি স্ট্রিংয়ে ইনপুট করতে চান, ডিফল্টরূপে এটিই নতুন লাইন। এই Xসংখ্যাগুলির স্ট্রিং যদি কেবল একক শব্দ / টোকেন হয় তবে এই কাজটি সহজেই সম্পন্ন করা যায় >>। অন্যথায় আপনি প্রথম সংখ্যাটি একটি পূর্ণসংখ্যার সাথে ইনপুট দিয়েছিলেন >>, cin.ignore()পরবর্তী লাইনে কল করুন এবং তারপরে আপনি যেখানে ব্যবহার করবেন একটি লুপ চালান getline()
0x499602D2

11

আপনি যদি নিম্নলিখিত পদ্ধতিতে আপনার প্রাথমিক কোডটি পরিবর্তন করেন তবে সবকিছু ঠিক থাকবে:

if ((cin >> name).get() && std::getline(cin, state))

3
ধন্যবাদ. এটি আরও কার্যকর হবে কারণ get()পরবর্তী চরিত্রটি গ্রাস করে। (std::cin >> name).ignore()আমি আমার উত্তরে আগে প্রস্তাবিত যা আছে ।
0x499602D2

"..কর্ম কর কারণ () ..." হ্যাঁ, ঠিক আছে। বিবরণ ছাড়াই উত্তর দেওয়ার জন্য দুঃখিত।
বরিস

4
কেন সহজভাবে নয় if (getline(std::cin, name) && getline(std::cin, state))?
ফ্রেড লারসন

0

এটি ঘটে কারণ নতুন লাইনের চরিত্র হিসাবে পরিচিত একটি অন্তর্নিহিত লাইন ফিডটি \nটার্মিনাল থেকে সমস্ত ব্যবহারকারীর ইনপুটগুলিতে যুক্ত হয় কারণ এটি স্ট্রিমটিকে একটি নতুন লাইন শুরু করতে বলে। std::getlineব্যবহারকারীর ইনপুটটির একাধিক লাইন পরীক্ষা করার সময় আপনি নিরাপদে এটির জন্য অ্যাকাউন্ট করতে পারেন । এর ডিফল্ট আচরণ এ ক্ষেত্রে ইনপুট স্ট্রিম অবজেক্ট থেকে std::getlineনতুন লাইন অক্ষর সহ সমস্ত কিছু পড়বে ।\nstd::cin

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::getline(std::cin, name) && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
    return 0;
}
Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in New Hampshire"
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.