1. C++ синтаксисі

1.1. Ең қарапайым бағдарлама

C++ тілінде екі санды қосатын ең қарапайым бағдарлама келесідей:

#include <iostream>

using namespace std;

int main() {
    int a, b;
    cin >> a >> b;
    int s = a + b;
    cout << s << endl;
    return 0;
}

Оны жол-жолға бөліп талдайық.

#include <iostream>

(Мұндағы бұрыштық жақшалар жай ғана < кіші және > үлкен таңбалар болып табылады.)

C++ тіліндегі # таңбасынан басталатын жолдар компилятор директивалары (немесе дәлірек айтқанда - препроцессорлық директивалар) деп аталады. Барлық басқа тілдік құрылымдардан айырмашылығы, олар бөлек жолға жазылуы керек.

#include <iostream> директивасы, шамамен айтқанда, пернетақтадан енгізу және экранға шығарумен жұмыс істеу мүмкіндігін береді. C++ тіліндегі #include директивасы Python тіліндегі import және Паскаль тіліндегі uss сияқты - бұл бағдарламада кейбір қосымша функциялар мен конструкцияларды пайдалануға мүмкіндік береді.

#include белгісінен кейін көрсетілген нәрсе тақырып файлы деп аталады. «Модуль» терминін қолданудың қажеті жоқ, ол Python және Pascal тілдерінде ұқсас жағдайда қолданылады; C++ модульдері мүлдем басқа жағдай (және шын мәнінде тек C++ 20 бастап қол жетімді).

Note

Айта кету керек, #include компилятор директивасы өте қарапайым мағынаға ие: ол көрсетілген файлдың мазмұнын алады (біздің жағдайда стандартты компилятор пакетіне кіретін iostream) және оны директива жазылған бағдарламаңыздағы орынға (include) енгізеді. Яғни, #include <iostream> " жазбасы компиляторға "iostream файлының мазмұнын ағымдағы файлдың берілген жерінде жазылғандай оқыңыз, содан кейін ағымдағы файлды әрі қарай оқыңыз" дегенді білдіреді. Сәйкесінше, стандартты `iostream тақырып файлы пернетақта мен экранмен жұмыс істеу үшін қажет функциялардың, деректер түрлерінің және т.б. заттардың тақырыптарын сипаттайды. Ол функциялардың толық кодын емес, тек атауларын жақсы сипаттауы мүмкін (код дайын кітапханаларда жинақталған болуы мүмкін), бірақ бұл тақырыптарды компилятор одан әрі сіздің бағдарламаңызды құрастыру үшін қажет етеді. Сондықтан, iostream сияқты файлдар тақырыптар деп аталады және көбінесе .h деп аяқталатын файлдарды оқиды.

Шын мәнінде, #include директивасын тек тақырып файлдарын қосу үшін ғана емес, сонымен қатар басқа файлдан кодты енгізу керек кез келген жерде пайдалануға болады. Бірақ сізге жақын арада қажет болмайтын өте сирек жағдайларды қоспағанда, мұны істеудің қажеті жоқ.

Сондай-ақ, #include директивасының болуының өзі С++ тілінің (дәлірек айтсақ, директива мұраланған Си тілі) өте ежелгі бағдарламалау тілі екенін көрсететінін атап өтейік. Басқа файлдардан кодты қандай да бір жолмен қосу қажеттілігі бағдарламалау тілдерінің басында пайда болғаны анық және бағдарламаның ағымдағы орнында басқа файлдың кодын жай ғана қамтитын конструкциялар жасаудың ең қарапайым тәсілдерінің бірі екені анық. Қазіргі заманғы тілдерде модульдер сияқты жүйелер бар, олар бір жағынан күрделірек (компиляторда іске асыру тұрғысынан), бірақ екінші жағынан дәлірек және бірқатар артықшылықтарға ие; Қазіргі тілдердегі #include сияқты конструкциялар тек кейбір қарапайым белгілеу тілдерінде немесе тілдің өзі өте қарапайым болған кезде ғана кездеседі.

Келесі жолға көшейік.

using namespace std;

Бұл жол, namespace std атаулар кеңістігін қосады. Онсыз көптеген стандартты функциялар, типтер, айнымалылар және т.б. std:: префиксімен жазу керек болар еді, мысалы, cin орнына std::cin жазу керек (cin біз бағдарламадан әрі қарай көреміз). Using namespace сізге жаңа мүмкіндіктерді пайдалануға мүмкіндік бермейді (#include - тен айырмашылығы), ол қазірдің өзінде қосылған мүмкіндіктерге қол жеткізу тәсілін өзгертеді.

C++ - дағы маңызды бағдарламаларда using namespace командаларын қолдану ұсынылмайды, бірақ біздің кішігірім бағдарламаларда оларды қолдануға болады.

Қазіргі уақытта атаулар кеңістігінің қандай екенін толық түсінудің қажеті жоқ, тек осы жолды есте сақтаңыз немесе келесі жазбаны оқыңыз.

Note

Барлық функциялар, типтер, айнымалылар және т.б. (әрі қарай қарапайымдылық үшін Мен тек функциялар туралы сөйлесемін) C++ атаулар кеңістігі бойынша бөлінеді. Бағдарламада дәл осылай жариялауға болатын барлық мүмкіндіктерге сәйкес келетін глобалды атаулар кеңістігі бар; сондай-ақ, сіз жаңа функцияларды жазған кезде оларды кез-келген атаулар кеңістігіне нақты енгізе аласыз. Әрі қарай, егер сіздің кодыңыз қандай да бір атаулар кеңістігінде болса, ол тек сол атаулар кеңістігінің функцияларына, сондай — ақ глобалды атаулар кеңістігінің (сонымен қатар іс жүзінде ата-аналық атаулар кеңістігінің функциялары - өйткені атаулар кеңістігінің құрылымы ағаш тәрізді) функцияларына тікелей жүгіне алады. Егер сіз басқа атаулар кеңістігінен функцияны шақыруыңыз керек болса, онда сіз бұл функцияның алдында атаулар кеңістігінің атауын және қос нүктені анық жазуыңыз керек, мысалы, other атаулар кеңістігінен fun функциясы other::fun деп аталады.

Мұның бәрі питондағы сияқты, егер сіз import math деп жазсаңыз, онда сіз квадрат түбір функциясын жай sqrt деп шақыра алмайсыз, math.sqrt деп жазуыңыз керек.

Бұл өте қарапайым мақсатпен жасалады: кез-келген бағдарламалау тілі үшін көптеген кітапханалар бар және әр кітапханада көптеген мүмкіндіктер бар. Әрине, әр түрлі кітапханаларда бірдей атаумен функциялар болуы мүмкін, мысалы, файлдар кітапханасында файлды ашу үшін open функциясы болуы мүмкін, ал желімен жұмыс істеу үшін

кітапханада open функциясы болуы мүмкін, мысалы, кейбір сайттармен байланыс орнатуда да open атауымен функция болуы мүмкін.

Сәйкесінше, егер сіздің бағдарламаңыз осы екі кітапханамен де жұмыс істеуі керек болса және сіз бағдарлама кодында open деп жазсаңыз, онда компилятор сізге қандай функция қажет

екенін түсінбеуі мүмкін. Бұл мәселені шешу үшін әр кітапхананың коды өзінің атаулар кеңістігіне орналастырылады, содан кейін атаулар кеңістігін нақты көрсете отырып, сіз компиляторға қандай функция қажет екенін түсіндіре аласыз.

Атап айтқанда, стандартты C библиотек кітапханасындағы барлық дерлік функциялар (әр түрлі қосымша кітапханалардан емес,атап айтқанда кез-келген компиляторға кіретін функциялар) std

атаулар кеңістігінде орналасқан. Тиісінше, егер сіз #include <iostream> деп жазсаңыз, сіз пернетақта мен экранмен жұмыс істеу мүмкіндігін қосқансыз, бірақ тиісті функциялар мен айнымалыларға std:: арқылы қол жеткізу керек(мысалы, std::cin).

using namespace сізге атаулар кеңістігінің атауын нақты білдірместен көрсетілген атаулар кеңістігіндегі функцияларды пайдалануға мүмкіндік береді. Атап айтқанда, using namespace std; деп жазу арқылы сіз стандартты функцияларды std:: префиксінсіз пайдалана аласыз.

Маңызды бағдарламаларда using namespace қолдану ұсынылмайды, өйткені атауы бірдей бірнеше функциялар бойынша мәселелер туындайды. Бірақ біздің кішігірім бағдарламаларымызда функция атаулары туралы шатасуыңыз екіталай, сондықтан әдетте using namespace std; кодын жазуға болады. (Кейбір мәселе шығуы мүмкін; мысалы, менің есімде, кейбір компиляторларда std::y1 функциясы бар. Егер сіз using namespace std; кодын жазсаңыз, айнымалыны y1 деп атай алмайсыз. Бірақ бұл тек кейбір компиляторларда кездесетін сияқты, және біздің бағдарламаларымызда мұндай жағдайларда айнымалының атын өзгерту оңайырақ.)

Айта кететін жайт, көптеген басқа тілдерде (модуль деген түсінік бар тілдерде), атаулар кеңістігі мен модуль екеуі бір мағына береді. Мысалы, питон тілінде import math деп бастасаңыз, ары қарай math.sqrt деп жаза аласыз немесе from math import * деп бастап, ары қарай тек sqrt деп қолдана бересіз. Бұл жердегі import math С++ -тағы #include кодының аналогы, ал from math import * болса, #include пен using namespace қосылған түрінің аналогы. Сондықтан, басқа тілдерде атаулар кеңістігі деген жеке түсінік кездеспейді, атаулар кеңістігі деген ол қарапайым модульдар.

«Атаулар кеңістігі» деген термин қызық көрінуі мүмкін, бұл негізі ағылшын тіліндегі namespace сөзінен құралған калька, бірақ мағынасы түсінікті: бұл кейбір «кеңістік», «атаулар» орналасқан аумақ - функциялардың атауы, айнымалылар, типтер және т.б. Сәйкесінше, С++-тегі барлық атаулар өзара қиылыспайтын кеңістіктер бойынша орналасқан. Және әрбір кеңістік «атаулар кеңістігі» деп аталады.

Келесі жол (ары қарай тексттер тек кодтар бойынша болады, азырақ ескертулермен):

int main() {

Бұл жол ешқандай аргументтерді қабылдамайтын және int түрінің мәнін қайтаратын "main" функциясын анықтайды (бұл бүтін сандар үшін ең стандартты деректер түрі). Бұл Паскальдағы function main:integer немесе питондағы def main(): жазбасының баламасы (тек питоннан айырмашылығы, C++ - да қайтару мәні қандай болатынын нақты көрсету керек, біздің жағдайда бұл int).

C++ - да, питоннан, Паскальдан және басқа да көптеген тілдерден айырмашылығы, кез-келген функциядан тыс жазылатын «бағдарламаның негізгі коды» деген ұғым жоқ. C++ - дағы кез — келген орындалатын код қандай-да бір функцияның бөлігі болуы керек, ал бағдарламаның ең негізгі коды функцияның ішінде арнайы main атауымен жазылуы керек. Басқаша айтқанда, бағдарламаны C++-да бастаған кезде main деп аталатын функция автоматты түрде іске қосылады. Ол C++ - дағы кез-келген бағдарламада болуы керек, ол дәл біреу болуы керек және жоғарыда айтылғандай, ешқандай параметрлерді қабылдамауы керек (бірақ нақты параметрлерді қабылдай алатын опция бар-олар команда жолының параметрлерін беру үшін қолданылады — бірақ бұл сізге әлі қажет емес), және int қайтару керек (бұл туралы кейін сөйлесейік).

Жалпы, біз төменде функциялардың синтаксисі туралы айтатын боламыз, бірақ әзірге бағдарламаның негізгі коды осындай жолдан басталуы керек екенін есте сақтаңыз.

Мұндағы ашылатын пішінді жақша { функция кодының басталғанын көрсетеді. Ол сәйкес жабылатын пішінді жақшаға } дейін жалғасады (Паскаль тіліндегі begin/end сияқты; Python-дан айырмашылығы, C++ тіліндегі шегініс (tab) компилятор үшін маңызды емес).

int a, b;

Бұл жол int түріндегі екі айнымалыны жариялайды, айнымалылар a және b деп аталады. Еске сала кетейін, int бүтін сандар үшін ең көп қолданылатын деректер түрі болып табылады; біз төменде бар деректер түрлері туралы көбірек айтатын боламыз. Бұл жазбамен a және b айнымалыларында нақты не жазылатынына кепілдік жоқ екенін ескеру маңызды. Олар кез келген мәнді қамтуы мүмкін; атап айтқанда, онда нөлдер жазылатынына мүлдем кепілдік берілмейді. Кейбір компиляторлар барлық айнымалы мәндерді нөлге қояды, бірақ басқаларында олай емес. Іс жүзінде, кейбір жағдайларда инициализацияланбаған айнымалыны пайдалану undefined behavior болып табылады (төменде қараңыз), яғни бұл жағдайда бағдарлама өзін ұнататын кез келген жолмен әрекет ете алады. Сондықтан, әрқашан, егер айнымалы мәндерді инициализациялау маңызды болса, олардың неге тең болуы керектігін нақты көрсетіңіз (бұл туралы төменде толығырақ). Біздің жағдайда бұл әлі маңызды емес, өйткені біз бұл айнымалыларды пернетақтадан енгіземіз.

cin >> a >> b;

Пернетақтадан a және b айнымалы мәндерін енгізіңіз. Өте ерекше синтаксиске назар аударыңыз. cin айнымалысы пернетақтадан енгізу ағыны деп аталады (консольдық кірістен), екі «үлкен» белгісі деректер ағынының бағытын көрсететін көрсеткішке ұқсайды: cin-ден a және `` b`` -ға дейін. Осылайша айнымалылардың кез келген санын енгізуге болады, жай ғана >> және айнымалының атын қосыңыз.

C++ - да пернетақтаны енгізу бірінші жуықтауда маңызды емес, сандар бос орындармен немесе жол аудармаларымен бөлінеді. Жоғарыда жазылғандай жазу пернетақтадағы санды есептейді, егер олар бар болса, алдымен қосымша бос орындарды немесе жол аудармаларын өткізіп жібереді, содан кейін тағы бір санды есептейді, тағы да оның алдындағы бос орындар мен жол аудармаларын өткізіп жібереді.

Note

Бұл «ағынды» енгізу, әрине, Python-ның input() арқылы енгізуіне қарағанда әлдеқайда ыңғайлы, мұнда әр жолға қанша сан енгізілгенін ойластыру керек. Python-да ағындық кірістің жоқтығы таңқаларлық болуы мүмкін, бірақ шын мәнінде бұл таңқаларлық емес: нақты өмірде ағынды енгізу сирек қажет; Енгізілген деректерде бос орындармен немесе жол үзілімдерімен бөлінген сандар болатындай жағдайлар - бұл олимпиадалардың ерекшеліктері және шынайы өмірде олар өте сирек кездеседі.

int s = a + b;

Біз жаңа айнымалыны бастаймыз, s, сонымен қатар int сияқты және оған бірден a және b сандарының қосындысын жазамыз. Айнымалыны құру кезінде оған қажетті мәнді бірден жазуға болады. = белгісінің оң жағында, әрине, кез-келген өрнек болуы мүмкін, оның ішінде егер бізге қандай сан қажет екенін бірден білсек соны ензігуге болады (яғни, мысалы, int cnt = 0; деп жазуға болады, егер айнымалыға нөлді жазғымыз келсе).

Жалпы алғанда, C++ тілінде пернетақтадан енгізу сияқты ерекше жағдайларды қоспағанда, барлық айнымалы мәндерді жасалған кезде бірден инициализациялау ұсынылады. Атап айтқанда, айнымалы мәндерді қажет болған сәтте ғана жасау ұсынылады. Паскаль тілінен ауысатын адамдар барлық қажетті айнымалыларды функцияның басында бірден жариялауды ұнатады - бұл қажет емес. Әрбір айнымалыны қажет болғанда ғана жариялаңыз; мысалы, мұнда біз s айнымалысын қажет болғанда ғана жариялаймыз. Сонымен қатар, көбінесе мұндай жағдайларда біз бірден мағыналы мәнді айнымалыға жаза аламыз, ал егер оны функцияның басында жариялаған болсақ, онда бұл мүмкін емес еді (біздің мысалда функцияның басында s айнымалысын жарияласақ, онда алдымен ол жерге мағыналы ештеңе жаза алмадық).

cout << s << endl;

Жауапты экранда көрсетеміз. Мұнда cout консоль шығысына жауапты айнымалы болып табылады және бұл жолы таңбалар қолданылғаннан азырақ, сонымен қатар деректер қозғалысының бағытын анық көрсетеді: s-ден cout-ға дейін. Әрі қарай endl шығарамыз - бұл арнайы айнымалы, оның cout ішіндегі шығысы жол арнасына әкеледі. (Шын мәнінде, мен төменде жазамын, сіз endl қолданбауыңыз керек, ол өте баяу. Бірақ жаңадан бастағандар үшін және жалпы алғанда шығыс деректерінің көлемі өте үлкен емес бағдарламаларда endl жазуға болады. ) (Сонымен қатар, осы нақты бағдарламада жол арнасы ерекше қажет емес екенін атап өтейін, өйткені біз бәрібір басқа деректерді шығармаймыз. Егер бізге басқа нәрсені шығаруды жалғастыру қажет болса, онда иә, жол арнасының мағынасы болуы мүмкін, әйтпесе бұл қажет емес.)

return 0;

Басқа тілдердегідей, return командасы функцияны тоқтатуды және көрсетілген мәнді шақырылған жерге қайтаруды білдіреді. Бірақ бұл жерде біз main негізгі функциясындамыз, сондықтан бұл команда бағдарламаны аяқтайды.

Ал мұндағы нөл бүкіл бағдарламаның қайтару коды (exit code) болады. Тұтастай алғанда, барлық операциялық жүйелерде жалпы қабылданған конвенция бар, әрбір іске қосылған бағдарлама операциялық жүйеге арнайы нөмірді қайтарады - қайтару коды деп аталады, ол бағдарламаның сәтті аяқталғанын немесе аяқталмағанын көрсетеді, сондықтан кім іске қосты бұл бағдарлама (ОЖ өзі немесе кез келген басқа бағдарламалар) қоңыраудың сәтті болғанын түсіне алады. Сондай-ақ, жалпы қабылданған конвенцияға сәйкес, нөлге тең қайтару коды бағдарламаның сәтті аяқталғанын білдіреді, ал нөлдік емес код қандай да бір қатенің орын алғанын білдіреді.

Мысалы, Code::Blocks қайтару кодын - exit code - аяқталғаннан кейін бағдарлама терезесінде жазады. Сол сияқты, тестілеу жүйелері бағдарламаның қайтару кодын талдайды және егер ол нөлге тең болмаса, сынақ нәтижесін «орындалу уақытының қатесі» немесе «нөлдік емес қайтару коды» (екеуі бірдей нәрсе) көрсетеді.

C++ тіліндегі main функциядағы return командасы сіздің бағдарламаңыздың қай қайтару кодын көрсету керектігін көрсетеді. Біз қайтару 0 деп жазамыз: бұл бағдарламаның сәтті аяқталғанын білдіреді. Мысалы, қайтару 1 деп жаза аламыз, содан кейін бағдарламаны басқарған адам бірдеңе дұрыс емес екенін түсінеді. Атап айтқанда, егер тестілеу жүйесіндегі кейбір тестте main return 1-мен аяқталса, онда сіз «орындалу қатесі» немесе «нөлдік емес қайтару коды» сияқты тест нәтижесін аласыз.

Басқа бағдарламалау тілдерінде қайтару коды түсінігі, әрине, бар, бірақ, мысалы, Python және Паскальда, егер орындалу негізгі кодтың соңына сәтті жеткен болса, онда қайтару коды нөлге тең болады деп есептеледі. Бірақ кейде сіз қайтару кодын нақты көрсету қажеттілігіне тап болған шығарсыз - мысалы, sys.exit(0) конструкциясында нөл бағдарламаны тоқтату керек қайтару коды болып табылады.

Дәл осы себепті main функциясы int түрін қайтаруы керек, сондықтан функция тақырыбы int main() { деп көрінеді.

Note

Шындығында, енді main функциясында return 0 жазудың қажеті жоқ - содан кейін ол нөлді қайтарады.

(Бірақ функция әлі де ''void'' емес, ''int'' ретінде анықталуы керек.) Бірақ әрқашан return 0 деп нақты жазған дұрыс, атап айтқанда, егер сіз return 0 деп нақты жазбасаңыз, көптеген ескі компиляторлар кездейсоқ қайтару кодын жасай алады. int қайтаратын басқа функцияларда return жазбау мүмкін емес.

}

Ақырында, бағдарламаның соңғы жолы main функция кодының аяқталғанын көрсететін жабылатын пішінді жақша болып табылады. Бұл паскальдың end сөзіне ұқсас.

1.2. Синтаксистің негізгі принциптері

C++ бағдарламасы (басқа тілдердегі сияқты) командалар тізбегі. Көптеген командалар нүктелі үтірмен аяқталуы керек.

Бағдарламаның құрылымы пішінді жақшалармен қалыптасады, яғни функция блоктары, егер блоктар, циклдар және т.б. пішінді жақшалар арқылы көрсетіледі. Python-дан айырмашылығы, C++ бағдарламасындағы шегініс (tab) компилятор үшін мағынасы жоқ. Компилятордың көзқарасы бойынша сіз қалағаныңызша шегініс жасай аласыз және әдетте бағдарламаны қалағаныңызша жолдарға бөле аласыз және т.б. (Кейбір ерекшеліктер бар, мысалы, компилятор директивалары, жоғарыдан қараңыз және бір жолды түсініктемелер, төменде қараңыз.) Дегенмен, әрине, шегіністерді Python тілінде орнатылғандай орнату ұсынылады (және факт, кез келген басқа бағдарламалау тілінде ) - бағдарламаны оқуды жеңілдету үшін.

C++ тілінде түсініктемелердің екі түрі бар: бір жолды - олар қатардағы екі қиғаш сызықтан (//) басталып, жолдың соңына дейін созылады, ал көп жолды - олар /* таңбасынан басталады және */ тармағынан аяқталады. Мысалы:

#include <iostream>

using namespace std;

int main() {
    int a, b;  // бұл түсініктеме
    cin >> a >> b;  /* және
    мынау
    да
    түсініктеме */ int s = a + b;
    cout << s << endl;
    return 0;
}

C++ тілі регистрге сезімтал (питон сияқты және Паскальдан айырмашылығы): бас әріптер мен кіші әріптер әр түрлі. Қарапайым бағдарламаларда тек кішкентай әріптерді қолдану әдеттегідей. Үлкен әріптер әдетте түрлерде (сынып атауларында) және глобал тұрақтылар (константа) мен макростардың атауларында қолданылады, біздің бағдарламаларда сізге сирек қажет болады.

Айнымалылар негізінен функциялардың ішінде анықталады, бірақ глобал айнымалылар да анықталуы мүмкін - олар барлық функциялардан тыс анықталуы керек:

#include <iostream>

using namespace std;

int a, b;

int main() {
    cin >> a >> b;  // енді бұл жерде глобал a мен b қолданылады
    int s = a + b;
    cout << s << endl;
    return 0;
}

Глобал айнымалылар төменде анықталған барлық функцияларда (бағдарлама коды бойынша) айнымалылардың өзінде көрінетін болады. Жалпы алғанда, глобал айнымалыларды пайдалану ұсынылмайды, бірақ қарапайым бағдарламаларда егер олар әртүрлі функцияларда шынымен қажет болса, оларды пайдалануға болады (мысалы, егер сіз тереңірек іздеуді жазсаңыз, онда графикті глобал етіп жасауға болады. айнымалы).

1.3. Бүтін деректер түрлері және толып кету

Питоннан айырмашылығы, онда түрі бүтін сандар үшін бір және ол қалағанынша сақтай алады үлкен сандар (қажет болған жағдайда ұзын арифметикаға ауысу), С++ -да бүтін сандар үшін әр түрлі типтер бар және әрқайсысының рұқсат етілген мәндер аралығының өз шекаралары бар. Бұл жағдайда түрлер қатаң анықталмаған; бір типтегі рұқсат етілген интервал әр түрлі компиляторларда немесе тіпті бір компилятордың әр түрлі нұсқаларында әр түрлі болуы мүмкін.

Мен мұнда барлық түрлерін тізімдемеймін, олар өте көп, мен сіз қолданатын негізгілерін ғана тізімдеймін:

  • int — негізгі, ең көп қолданылатын түрі.:math:-2^{31}-дан \(2^{31}-1\) дейін немесе (компиляторға және опцияларға байланысты) \(-2^{63}\)-дан \(2^{63}-1\) дейінгі сандарды сақтайды, сәйкесінше 4 немесе 8 байт алады.

  • unsigned int - қысқаша unsignedбелгіленбеген (санның белгісін сақтамайды, оның орнына сан мәнінің қосымша битін сақтайды) int-қа балама, 0-ден \(2^{32}-1\) дейін немесе \(2^{64}-1\) дейінгі сандарды сақтайды, сәйкесінше 4 немесе 8 байт алады (int сияқты).

  • long long int, немесе қысқаша long long\(-2^{63}\)-дан \(2^{63}-1\) дейінгі сандарды сақтайды, 8 байт алады.

  • unsigned long long int, немесе қысқаша unsigned long long — long long-тың белгіленбеген баламасы, 0-ден \(2^{64}-1\) дейінгі сандарды сақтайды, 8 байт алады.

  • size_t — бұл кез келген жарамды деректер түрінің (массивтерді қоса алғанда) өлшемі (байтпен) дәл осы түрге сәйкес келетініне кепілдік беретін жеткілікті үлкен, қол қойылмаған түр (бұл мүлдем нақты анықтама емес, бірақ мағынаға жақын) . Яғни, `` size_t`` кез келген басқа айнымалы иеленетін байттардың санын сақтауға кепілдік береді. Әдетте бұл unsigned баламасы немесе unsigned long long баламасы. Ол көбінесе кейбір стандартты функциялар нысанның өлшемін, массивтегі элементтердің санын немесе сол сияқтыларды қайтаратын жағдайларда қолданылады. (өйткені, жоғарыдағы анықтамаға сәйкес, бұл өлшем міндетті түрде size_t-ге сәйкес келеді, бірақ ол, мысалы, int-ге сәйкес келмеуі мүмкін). Ең қарапайым жағдайларда сіз бұл түрді өзіңіз қолданбайсыз, бірақ оны стандартты функциялардың сипаттамаларында көресіз.

Note

Жалпы айтқанда, компиляторлар немесе компиляция опциялары болуы мүмкін, онда бұл типтер одан да көп болады — жадтың мағынасында және сәйкесінше мәндер ауқымында. Бірақ іс жүзінде қазір мұндай компиляторлар жоқ. Сондай-ақ, жалпы айтқанда, int және сәйкесінше unsigned аз болуы мүмкін, мысалы, 2 байтты алады және тиісті мәндер диапазонына ие болады, бірақ компиляторларда толыққанды компьютерлер үшін (микропроцессорлар үшін емес, т.б.) сіз оны кездестіре алмайсыз. Айтпақшы, әрине, бекітілген компилятордың бекітілген опцияларында барлық түрлердің өлшемдері бекітілген, яғни бағдарламада екі int айнымалысын жарияладыңыз, олардың бірі 4 байт, екіншісі 8 байт болуы; немесе сіз бағдарламаны құрастырған болсаңыз, сізде int 4 байт болды, содан кейін ештеңені өзгертпестен, бірдей компилятормен бірдей опциялармен қайта құрастырылып 8 байт шығуы мүмкін емес.

C цел-дағы бүтін типтердің маңызды ерекшелігі (және кез-келген басқа тілде, бірақ питонда емес) **толып кету* * болып табылады. Егер сіз айнымалы мәнді оның түрінің рұқсат етілген диапазонынан тыс сақтауға тырыссаңыз, оның орнына рұқсат етілген диапазонға жататын басқа мән сақталады. Сонымен қатар, C++ - да ешқандай қате болмайды, тек үнсіз қате жауап аласыз.

Алдыңғы абзацтағы "сақтау" сөзі сіз осындай санды тікелей жазуға тырысқан жағдайларды (мысалы, int x = 12345678901234567890;) және кез-келген есептеудің нәтижесін сақтайтын жағдайларды білдіреді (int a = 1000000000; int b = a * a;) және деректерді енгізу жағдайларына және т.б. тәжірибе жасап көріңіз және оның қалай жұмыс істейтінін біле аласыз.

Сондықтан, әрдайым бүтін деректер түрлерімен жұмыс жасағанда, толып кету қаупі туралы ұмытпаңыз. Әрқашан бір немесе басқа айнымалыда қандай максималды мән пайда болуы мүмкін екенін бағалаңыз және оның түрге сәйкес келетінін тексеріңіз. Егер ол 4 байтты int - ге сәйкес келмесе, long long айнымалысын жасаған дұрыс (жалпы айтқанда, барлық айнымалыларды long long жасауға ешкім кедергі жасамайды, бірақ содан кейін сіз кейбір үлкен массивтердің жад шектеулерінен өтпеу қаупін тудырасыз, сонымен қатар long long да толып кетуі мүмкін). Егер сіз жауаптың тіпті long long-қа сәйкес келмейтінін көрсеңіз, онда сіз қазірдің өзінде ойлануыңыз керек. Мүмкін, белгілі бір компиляторда 16 биттік бүтін сан түрі бар (int128_t немесе __int128 сияқты), бірақ бұл әрдайым бола бермейді, сонымен қатар ол толып кетуі мүмкін. Немесе ұзын арифметиканы қолдану керек. Немесе мұндай Үлкен сандар пайда болмайтын басқа алгоритм ойлап табыңыз.

Белгілердің толып кетуінің жиі және өте айқын белгісі (int және long long) - бұл теріс сан болуы мүмкін емес жауап (мысалы, оң сандардың қосындысы) әлі де теріс болып шығады. Егер сіз мұны бағдарламаңызда байқасаңыз-толып кету жағын іздеңіз.

Сондай-ақ, қол қойылмаған түрлерді қажетсіз пайдалануды ұсынбаймын. Олардағы өте жиі кездесетін қателік - бұл төмен, төмен қарай толып кету деп аталатын: мысалы, 0-ден 1-ді алып тастауға тырыссаңыз, нәтиже -1 болмайды (өйткені таңбасыз түрлер теріс сандарды сақтай алмайды), бірақ өте үлкен сан. Атап айтқанда, әдеттегі қате - кейбір массивтің немесе жолдың ұзындығынан біреуін алып тастау: бұл ұзындықтар әдетте size_t өлшемімен өлшенетіндіктен, жол ұзындығы нөлге тең болса, толып кету орын алады. Алдымен ұзындықты int ішінде сақтау дұрыс, содан кейін 1-ді алып тастау немесе түрлерін беру керек, төменде қараңыз.

Note

Толып кетудің нәтижесі қандай? Белгіленбеген түрлерді толтырған кезде (unsigned, unsigned long long, size_t және т.б.), :math: 2^x-ке бөлгендегі қалдықтың модульі алынады, мұнда :math: x – осы деректер түріндегі биттердің саны (жоғарыдағы түрлер үшін 32 немесе 64). Мағынасы қарапайым – белгіленбеген түрі бар кез келген операциялар кезінде ең аз маңызды :math: x биттері ғана сақталады және барлық қосымша биттер жойылады.

Белгісі бар типтердің толып кетуі анықталмаған. Бұл undefined behavior деп аталады (төменде қараңыз) - егер бұл өте қарапайым болса, онда белгілердің толып кетуінің салдары, соның ішінде int, тіпті бағдарламаның құлауын қоса алғанда, кез келген нәрсе болуы мүмкін.

Мен сондай-ақ типті келтіру туралы айтайын («келтіру» сөзінен – сіз бір түрді екіншісіне ауыстырасыз, яғни басқа түрге түрлендіресіз; оны сондай-ақ ағылшынша cast «каст» дейді). Мән түрін жаңа түрдегі айнымалы мәнде сақтау арқылы әрқашан түрлендіруге болады:

unsigned x = ....;
int y = x;  // x unsigned болды және оны int-те сақтадық
cout << y - 1;  // енді толып кетуден қорықпай 1-ді шегеруге болады

Бірақ артық айнымалыларды бастамау үшін Сіз қажетті түрге ие болатын өрнекті жаза аласыз. C++ стиліндегі жазбаның толық пішіні келесідей: static_cast<int>(x), мұнда бұрыштық жақшаларда (тағы да, бұл тек қана үлкен емес таңбалар) қай типті көрсеткіңіз келетінін көрсетесіз, ал жақша ішінде - шығарғыңыз келетін айнымалы мәнді бересіз. Бұл жазба * өрнек*, яғни оны бір жерде сақтауға немесе басқа өрнектерде қолдануға болады. Мысалы, келесідей:

unsigned x = ...;
cout << static_cast<int>(x) - 1;  // алдымен int-ке түрлендірілді, содан кейін 1 шегерілді

Сондай-ақ C стиліндегі белгі бар: (int)x, мысалы

unsigned x = ...;
cout << (int)x - 1;  // алдымен int-ке түрлендірілді, содан кейін 1 шегерілді

Бірінші жуықтау үшін бұл бірдей, бірақ күрделі түрлерде static_cast қолданған дұрыс.

Әрине, static_cast тек бүтін сан түрлеріне қатысты емес; сіз әртүрлі типтерді көрсете аласыз, мысалы, нақты түрі: static_cast<double>(x) (double түрі үшін, төменде қараңыз). Қандай түрлерге әкелуге болатыны туралы қатаң ережелер өте күрделі және әдетте өте қатал (мысалы, санды жолға түрлендіру немесе керісінше static_cast арқылы жұмыс істемейді), бірақ сіз тәжірибе жасап көре аласыз.

1.4. Арифметикалық амалдар

Қосу, алу және көбейту +, - және * көмегімен басқа тілдердегідей орындалады, мұнда ерекше ештеңе жоқ. Көрсеткіштерге арналған арнайы оператор жоқ, циклды жазыңыз :) (немесе жағдайға байланысты жылдам дәрежеге шығару немесе pow).

Бірақ бөлудің кейбір ерекшеліктері бар. Жартылай бөлікті / операторы, қалдығын % операторы қабылдайды, бірақ екі бүтін санды нақты алу үшін бөлудің тікелей жолы жоқ (яғни C++ тілінде / — Python //, бірақ Python / аналогы жоқ). Нақты санды бөлуді алу үшін сандардың кем дегенде біреуі нақты сан екеніне көз жеткізу керек.

Мысалы:

int x = 10, y = 3;
cout << x / y;  // 3-ті қайтарады
cout << 1.0 * x / y;  // бөлінгішті нақты санға өзгерттік, жауабы 3.33333 шығады

Ерекше, бірақ өте маңызды жағдай - 1/2 жазбасы нөлді береді. 0.5 алу үшін, мысалы, 1.0/2 (немесе тікелей 0.5, әрине) жазу керек.

Бөлудің екінші ерекшелігі-теріс сандарды өңдеу. Егер сіз теріс санды оң санға бөлудің қалдығын алсаңыз, онда қалдық теріс болады. Бұл қисынды болып көрінуі мүмкін, қарама-қайшы болып көрінуі мүмкін (және бұл шын мәнінде қисынсыз), бірақ питонда олай емес және көптеген жағдайларда сізге кедергі болады. Бұл мәселені айналып өтудің стандартты тәсілі - (a%b+b)%b деп жазу, яғни бір қалдықты алғаннан кейін b қосу (нақты оң Сан алу үшін) және қалдықты қайтадан алу. Немесе if деп жазыңыз. Сол сияқты, теріс санды оңға бөлудің толық емес бөлігін есептеу кезінде жауап сіз күткеннен 1-ге өзгеруі мүмкін.

Егер бөлгіш теріс сан болса, онда ол әлі де қиын болуы мүмкін.

Note

Толығырақ. Қалдықпен бөлудің анықтамасы өте қарапайым: бүтін санды бөлу \(A\) натурал санға \(B\) — бұл екі адам санын табу \(R\) (толық емес бөлшек) және: math:Q '(қалдық), бұл: math: `A = R cdot B + Q, содан кейін келесі талаптарды қою керек :math:` Q` (немесе :math:`R').

Классикалық анықтама келесі шартты орындауды талап етеді \(0\leq Q<B\), яғни қалдық теріс емес және \(B\) - дан аз . Питон дәл осы анықтаманы ұстанады. Мысалы, (-10) // 3 = -4 және (-10) % 3 == 2 (себебі -10 == 3 * (-4) + 2). Бұл біртүрлі көрінуі мүмкін ((-10) // 3 шешімі -3 боп көрінуі мүмкін), бірақ бұл шын мәнінде қисынды.

Бірақ барлық заманауи процессорлар басқаша ойлайды (шамасы, бұл тарихи түрде болған, ал қазір процессорлардың бар мінез-құлқын өзгерту мүмкін емес). Егер \(A>0\) болса, онда олар бірдей анықтаманы қолданады. Ал егер \(A<0\) болса, онда \(-B<Q\leq 0\) осы өрнекті орындау керек болады. Бұл анықтамамен (-10) // 3 == -3 және (-10) % 3 == -1 шығады. Нәтижесінде бәрібір \(A = R \cdot B + Q\) болады, сол үшін \(Q\) бұл нұсқада \(B\) -дан кіші алдыңғы нұсқаға қарағанда (-1 2-нің орнына егер B==3 мысалда болса), ал \(R\) бірлікке үлкен, бірақ бұл әлі де ыңғайсыз.

Python бұл әрекетке арнайы жеңілдіктер жасайды, ал C++ (және көптеген басқа тілдер) процессор қайтарған нәтижені жай ғана пайдаланады.

Мұның бәрі бөлгіш (\(B\)) оң болғанда болды. Теріс бөлгішпен бәрі әдетте күрделірек.

1.5. Меншіктеу, auto және ++

Меншіктеу жалғыз теңдік арқылы жасалады:

s = a + b;

(Бұл сізде жаңа мәнді жазғыңыз келетін s айнымалысы бар деп болжайды.)

Питондағыдай қысқартылған меншіктеу операторлары да бар: +=, -=, *=, /=, %=.

Сондай-ақ, меншіктеулерді айнымалыны жариялау кезінде бірден қолдануға болатынын көрдік:

int a = 10;

Бұл жағдайда белгілі бір түрдің орнына арнайы auto сөзін қолдануға болады, ол «өрнектің оң жағындағы түрді пайдалану» дегенді білдіреді (бұл тек C++11 тілінде пайда болды):

int a, b;
...
auto c = a + b;  // a+b өрнек түрі int, сондықтан с айнымалысы да int

auto a = 10 белгісі өте анық емес (10 санының қай түрі - int? unsigned? long long?..), сондықтан оны қолданбау керек. Бірақ оң жақта күрделі өрнек болса, онда auto қолдануға әбден болады.

Сондай-ақ айнымалыны 1-ге арттыру немесе азайтуды білдіретін ++ және -- арнайы конструкциялары бар:

int a = 10;
a++;  // a-ны 1-ге арттырсақ, a == 11 шығады
a--;  // 1-ге азайту арқылы 10-ға қайтарамыз

Шын мәнінде, бұл операторларды жазудың екі нұсқасы бар: a++ және ++a және сол сияқты -- түрінде де. Екеуі де a мәнін бір-біріне арттырады, бірақ қайтару мәні бойынша ерекшеленеді. (ол b = a++ сияқты бірдеңе жазған болсаңыз немесе мысалы, foo(a++) функциясын шақырсаңыз пайдаланылады). a++ жазу кезінде қайтару мәні ескі a мәніне тең болады (мысалы, алдымен a мәнін есте сақтаңыз, содан кейін оны 1-ге көбейтіңіз), ++a болғанда - жаңа мән (бірінші ұлғайту, содан кейін a мәнін пайдалану сияқты) және сол сияқты -- үшін де:

int a = 10;
int b = a++;  // b 10-ға тең болады
int c = --a;  // с да 10-ға тең болады

Бірақ жалпы алғанда, ++ және -- операторларының нәтижесін пайдалану жаман тәжірибе, оны жасамаңыз. a++ бөлек команда ретінде жазыңыз, содан кейін ешқандай проблемалар болмайды.

Түбірді табу sqrt көмегімен есептеледі, ол үшін cmath тақырып файлын қосу керек (#include <cmath>). Модуль abs көмегімен есептеледі.

1.6. Кіріс-шығыс

Жоғарыда көргеніміздей, пернетақтадан енгізу cin нысаны арқылы, экран шығысы cout арқылы жүзеге асырылады:

#include <iostream>

.....

int a, b;
cin >> a >> b;
cout << a + b;

Сонымен қатар, мұндай оқу автоматты түрде қосымша бос орындарды өткізіп жібереді және жаңа жолдарға өтеді, сондықтан екі санның бір жолда немесе басқаларында болуы маңызды емес. Егер бұл сіз үшін маңызды болса (мысалы, деректерді тек бір жолдан оқу керек), онда ол күрделірек; ең оңай жолы - stringstream пайдалану, жолдар туралы бөлімде төменде қараңыз.

Шығару кезінде жолды беру endl жазу арқылы жүзеге асырылады немесе арнайы таңбаны немесе '\n' немесе "\n" жолын басып шығаруға болады (бұл жағдайда тырнақшалар немесе апострофтар қолданылғаны маңызды емес, бірақ жалпы жолдар мен белгілер туралы төменде қараңыз).

cout айнымалылар арасына бос орындар енгізбейтінін ескеріңіз (Python-ның print-інен айырмашылығы). Қажет болса, оларды өзіңіз енгізіңіз. Сондай-ақ, кіріс деректерін бүтін санға арнайы түрлендіруді жазудың қажеті жоқ екенін ескеріңіз (Python int()-дан айырмашылығы). Айнымалы мәнді int деп жариялап қойсаңыз жеткілікті.

Жоғарыда «C++ стилі» енгізу/шығару сипатталады. С стилінде енгізу/шығару printf және scanf функциялары арқылы орындалады. Мен оларды сипаттамаймын, олар айтарлықтай күрделі, егер сіз оларды бір жерден көрсеңіз, таң қалмаңыз.

1.7. Шартты оператор (if) және логикалық операциялар

Осылай жазылады:

if (шарт) {
    код
} else {
    код
}

else бөлімін жазбаса да болады (егер қажет болмаса):

if (шарт) {
    код
}

Бұл жерде төмендегілер маңызды. Біріншіден, шарт жақшаға алынуы керек.Екіншіден, кодтың өзі пішінді жақшаға алынады; олар if ішінде қандай код бар екенін анықтайтындар. Егер if ішінде тек бір команда болса пішінді жақша жазудың қажеті жоқ. Бірақ команда өте қарапайым болмаса, бұл нұсқа ұсынылмайды.

Python тіліндегідей жағдайда сіз салыстыруларды пайдалана аласыз (>, >=, <, <=, ==, !=), салыстыру қос теңдік арқылы жасалатынын ескеріңіз (шын мәнінде, Python тіліндегідей және Паскальдан өзгешелеу).

Мұндағы маңызды мәселе, егер сіз қос теңдіктің орнына бір теңдік жазсаңыз, C++ қате жібермейді:

if (a = b) {...}

бірақ бұл енді салыстыру емес, бұл тағайындау! сондықтан ол сіз ойлағандай жұмыс істемейді. Бұл өте жиі кездесетін қате, әсіресе Паскальдан ауысатындар арасында. Python бұл жағдайда қате жібереді, бірақ C++ та олай емес.

Логикалық амалдар былай жазылады: and — &&, or — ||, not — !. Мысалы:

if ((year % 400 == 0) || (year % 4 == 0 && !(year % 100 == 0)))

(әрине, былай да жазса болады year % 100 != 0).

C++ тілінде elif конструкциясы жоқ. Бірақ бұл қажет емес - сіз жай ғана else if деп жаза аласыз:

if (...) {
    ...
} else if (...) {
    ...
} else if (...) {
    ...
} else {
    ...
}

Python-да сіз оны осылай жаза алмайсыз, себебі әрбір else/if шегіністерді көбейтуді талап етеді және бірнеше шегіністер пайда болады. Бірақ C++ тілінде шегініске қатаң талаптар жоқ, сондықтан оны дәл осылай жазуға әбден болады.

1.8. Циклдер

while циклі сіз күткендей жазылған:

while (шарт) {
    код
}

if сияқты, мұнда да шартты жақшаға алу керек, ал цикл денесі пішінді жақшада, ерекшелік — егер цикл денесі бір командадан тұрса, жақшаларды қоймаса болмайды (бірақ бәрібір ұсынылады). while циклі басқа тілдердегідей жұмыс істейді.

Бірақ C++ тілінде «for» циклі жазылған және әдеттен тыс жұмыс істейді. Ең қарапайым жағдайда ол былай жазылады:

for (int i = 0; i < n; i++) {
    код
}

Бұл Python-ның for i in range(n): эквиваленті - i айнымалысы 0-ден бастап n-ға дейін (қоспағанда) барлық мәндер арқылы өтеді.

Жалпы, for тақырыбы нүктелі үтірмен бөлінген үш бөліктен тұрады. Бірінші бөлім (жоғарыдағы мысалдағы int i = 0) цикл алдында не істеу керек (бұл жағдайда i айнымалысын жариялаңыз және сол жерге нөлді жазыңыз). Екінші бөлік (i < n) циклды жалғастыру шарты болып табылады: бұл шарт циклдің ең бірінші итерациясына дейін және әрбір итерациядан кейін тексеріледі және шарт жалған болған кезде циклдің орындалуы аяқталады. (while шартына ұқсас). Үшінші бөлік (i++) - шартты тексермес бұрын әрбір итерациядан кейін не істеу керек екені жазылады.

Яғни, жоғарыдағы жазба мынаны білдіреді: i айнымалысын бастаңыз, нөлді жазыңыз, содан кейін i<n рас екенін тексеріңіз, егер солай болса, цикл денесін орындаңыз, содан кейін i++ жасаңыз, қайтадан i<n тексеріңіз, егер әлі де орындалса, қайтадан орындаңыз код және i++ жасаңыз және т.б., келесі сәтте i>=n болғанға дейін.

Мысалдар:

for (int i = n - 1; i >= 0; i--)  // кері цикл
for (int i = 0; i < n; i+= 2)  // 2-қадаммен цикл
for (int i = 0; !found && i < n; i++)  // found true немесе i >= n болғанда цикл аяқталады
for (int i = 1; i < n; i *= 2)  // екі дәрежелі цикл

Яғни, шын мәнінде, C++ тіліндегі for циклдің өте күшті түрі болып табылады, тіпті әдеттегі while for-дың ерекше жағдайы болып табылады. Бірақ қандай да бір жолмен дәйекті түрде өзгеретін анық "цикл айнымалысы" бар жағдайларда ғана for пайдалану ұсынылады, содан кейін for тақырыбында сіз оны тек еске саласыз. Егер сізге күрделірек нәрсе керек болса, while -ды жазыңыз.

Сондай-ақ, цикл айнымалысы тікелей цикл тақырыбында жарияланатынын ескеріңіз. Атап айтқанда, мұндай айнымалы циклден тыс көрінбейді - дұрыс, егер сіз for циклін жазып жатсаңыз, циклден кейін цикл айнымалысын пайдаланудың қажеті жоқ. Сонымен қатар, бұл, мысалы, бірдей айнымалысы бар қатарға екі «for» циклін жазуға мүмкіндік береді, Сонымен қатар, бұл айнымалылар бір типте болуы міндетті емес:

for (int i = 0; i < n; i++) {
    код, мұндағы i -- int
}
// мұнда i айнымалысы мүлде жоқ
for (unsigned int i = 1; i < m; i *= 2) {
    код, мұндағы i -- unsigned
}

C++ 11 тілінде пайда болған for циклінің тағы бір түрі бар - range-based for деп аталады. Бұл Python-ның for ... in таза аналогы, ол сізге range-дан артық емес, кез келген нысанның (массив, жол және т.б.) көп немесе азырақ қайталануына мүмкіндік береді. C++ тілінде ол былай жазылған:

for (int i : v) {
    код
}

мұнда v int массиві болып саналады, содан кейін i осы массив элементтерінің барлық мәндерін дәйекті түрде қабылдайды.

Атап айтқанда, auto пайдалану жиі ыңғайлы:

for (auto i : v) {
    ...
}

i айнымалысы массив элементтерімен бірдей типке ие болады.

break және continue командалары бар және Python және Pascal тілдеріндегідей жұмыс істейді; атап айтқанда, while (true) жазуға болады, содан кейін кодта break қолдануға болады.

Сонымен қатар, итерациядан кейін шартты тексеретін do-while циклі бар; мен оны сипаттамай-ақ қояйын (бұл жерде күрделі ештеңе жоқ), оның қажеті көп болмайды (дәлірек айтқанда, ешқашан керек емес; бұның python-да баламасы жоқ екені кездейсоқ емес).

1.9. Массивтер

C++ тіліндегі массивтер келесідей жарияланады:

#include <vector>
....
vector<int> v;

Бұл int сақталатын бос (ұзындығы нөл) массивті (сонымен қатар түрдің атауынан кейін жиі «вектор» деп аталады) жариялайды. Сондай-ақ бұрыштық жақшаға басқа түрді жазуға болады - сәйкесінше, сәйкес типтегі элементтер массиві болады. Атап айтқанда, екі өлшемді массив келесідей жасалады: vector<vector<int>> массив, оның әрбір элементі int массиві болып табылады.

(vector<vector<int>> белгілеуіндегі >> конструкциясы C++11 мүмкіндігі болып табылады. Бұрынғы стандарттарда >> белгісі деректерді енгізу операторы ретінде анық қарастырылған және екі өлшемді массивті анықтау екі жақшаның арасын бос орынмен үшін жазу керек болды vector<vector<int> >.)

Note

int x; сияқты сандық айнымалы мәнді инициализацияламасаңыз, оның мәні анықталмаған және оны пайдалану мүмкін емес екенін ескеріңіз. Егер сіз C++ массивін инициализацияламасаңыз, жай ғана vector<int> v; деп жазсаңыз, онда оның бос болуына кепілдік беріледі. C++ тілінде күрделірек деректер құрылымдары бірдей жұмыс істейді: жолдар, сөздіктер...

Массивтің ұзындығын бірден көрсетуге де болады:

vector<int> v(n);

бұл массивтің ұзындығы n-ға тең. Ол нөлдермен толтырылады, бірақ оған сенуге болмайды; нөлді нақты көрсетіңіз (өйткені деректер инициализацияланбаған кезде бірнеше ұқсас құрылымдар бар). Массивті қандай мәнмен толтыру керектігін нақты көрсету үшін бұл мән ұзындығынан кейін көрсетілуі керек:

vector<int> v(n, 1);

бұл массив бірліктермен толтырылған.

Сондай-ақ, массив элементтерін пішінді жақшаға нақты тізімдеу арқылы жасауға болады:

vector<int> v{-1, 0, 1};

— бұл -1, 0, 1 элементтері бар ұзындығы 3-ке тең массив.

Нөлдермен толтырылған екі өлшемді массив келесідей жасалады:

vector<vector<int>> v(n, vector<int>(m, 0));

Мұнда не жазылған? Басталуы түсінікті: vector<vector<int>> v(n, - массивтер жиымы, сыртқы массивтің ұзындығы n. Содан кейін ол әрбір элементтің неге тең болуы керектігін жазылған: vector<int>(m, 0) — бұл нөлдермен толтырылған ұзындығы m атауы жоқ массив деп айтуға болады. Ол сыртқы массивтің элементтері үшін мән ретінде көрсетілгендіктен, бұл ұзындығы m массив көшіріліп, ұзындығы n сыртқы массивімен толтырылады. Соңында n x m болатын екі өлшемді массив дайын болады.

Сол сияқты көп өлшемді массивтерді құруға болады. Бірақ Python-дан айырмашылығы, C++ тілінде бір массивтің барлық элементтері бірдей түрге ие болуы керек; кейбір элементтері сандар, ал кейбіреулері массивтер және т.б. болатын массив жасай алмайсыз. (Бірақ шын мәнінде бұл сізге әдетте қажет емес.)

Массив элементтеріне тік жақшалар арқылы қол жеткізуге болады: v[i], екі өлшемді v[i][j] массиві үшін (Паскальдан өтетіндер үшін: v[i][j] жазбасы құрастырылғанын ескеріңіз, бірақ ол сіз қалағандай жұмыс істемейді). Массив элементтері Python тіліндегі сияқты нөлден бастап индекстеледі. Python тіліндегідей теріс индекстеу жоқ: v[-1] жазуы массивтағы шектен тыс болып табылады.

C++ тіліндегі массивтің шегінен шығу міндетті түрде қатеге әкелмейді. Дәлірек айтқанда, ол кез келген нәрсеге әкелуі мүмкін, қарапайым жағдайларда ол жай ғана массивтен тыс жадпен жұмыс істейді, мүмкін сізге қажет кейбір басқа деректер қайта жазылады және т.б., егер сіз массивтен өте алыс кеткен болсаңыз, онда бағдарлама апат. Бірақ қатаң айтқанда, массивтің шегінен шыққанда, кез келген нәрсе болуы мүмкін, бұл анықталмаған мінез-құлық деп аталады, бұл туралы төменде қараңыз.

Python-ға қарағанда массивтерде қол жетімді амалдар азырақ. Негізгілері push_back (элементті массивтің соңына қосады, питондағы append баламасы, v.push_back(x);) және pop_back операциялары (массивтің соңғы элементін жояды: ` v.pop_back();`). Массив тағайындау да жұмыс істейді (v2 = v;) және питоннан айырмашылығы, массив іс жүзінде көшіріледі: одан кейін v2 және v әртүрлі массивтер болып табылады және біреуіндегі өзгертулер екіншісіне әсер етпейді. Массивтерді кез келген салыстыру операторлары арқылы да салыстыруға болады (>, < және т.б., соның ішінде ==). == операторы екі массивтің бірдей екенін тексереді, яғни элементі бойынша тең екенін қарайды; Үлкен-кіші салыстыру операторлары массивтерді лексикографиялық түрде салыстырады. Массивтің ұзындығын v.size() арқылы білуге болады.

Сондай-ақ басқа да көптеген операциялар бар, бірақ сіз олардың қаншалықты күрделі екенін түсінбейінше, оларды қолданбауыңыз керек.

>> және << көмегімен массивтерді тікелей енгізу және шығару мүмкін емес; сіз әрқашан цикл жазуыңыз керек (бірақ ағынды енгізу есебінен, яғни >> оператор маңызды емес, сандар бос орындармен немесе жол үзілімдерімен бөлінген, массив енгізу өте оңай, әсіресе массивте қанша элемент болатыны алдын ала берілген болса).

Әдеттегі мысал: егер сіз алдымен массивтегі элементтердің санын, содан кейін массивтің өзін енгізсеңіз, онда оны былай жазуға болады:

int n;
cin >> n;
vector v(n);
for (int i = 0; i < n; i++) {
    cin >> v[i];
}

Біз v айнымалысын қажет болғанда ғана жариялайтынымызды және осыған байланысты массивтің қажетті ұзындығын бірден көрсете алатынымызды ескеріңіз. Біз айнымалыларды жариялау кезінде массив элементтері үшін нақты мәндерді көрсетпейміз, өйткені біз оларды әлі де пернетақтадан енгізетін боламыз.

Python тілімдерінің (slice) тікелей аналогы жоқ.

Векторлардан (vector) бөлек, шикі массивтер деп аталатындар да бар. Олар былай жарияланады:

int a[10];
// немесе
int* a = new int[10];

Бұл C стиліндегі массивтер; оларды пайдаланудың қажеті жоқ.

1.10. Символдар мен жолдар

С++ тіліндегі символ деректерінің түрі char деп аталады, символдық тұрақтылар бір апострофпен жазылады (тырнақша емес!).

char-дың әдеттен тыс ерекшелігі - Python және Pascal тілінен айырмашылығы, C++ тілінде char бірден бүтін сан түрі, компилятордың көзқарасы бойынша ол бүтін санды сақтайды. C++ тілінде таңбаны оның кодына және керісінше аударатын ord және char сияқты операциялар жоқ. C++ тілінде символ мен оның коды бір және бір нәрсе. Айнымалыға символ жазып, одан кейін бірнеше санды қосуға болады немесе мысалы, екі символды шегеруге болады.

Мысалдар:

char a = 'A';  // Жарайды, бәрі түсінікті, бұл Python және Pascal тілдеріндегідей
a += 10;  // символға 10 қосуға болады, бұл коды 'A' мәнінен 10 үлкен символны береді
int diff = 'a' - 'A';  // біз екі таңбаны алып, int аламыз (немесе char да ала аламыз)
char b = 'B';
b += diff;  // шығатын мәні 'b'
int x = b;  // мәнді x-ке көшіріңіз - енді 'b' символының коды x-та
char z = '9';
int value = z - '0';  // сонымен, символ-саннан осы санның нақты мәнін алуға болады

Басқаша айтқанда, C++ тіліндегі символдар сандарды жазудың тағы бір жолы ғана. 'A' белгісі мен 65 іс жүзінде бірдей нәрсе.

char және басқа бүтін сан түрлерінің арасындағы жалғыз айырмашылық - енгізу/шығару. char түріндегі айнымалы мәндерді енгізу және шығару кезінде сәйкес символдар басып шығарылады. Барлық басқа аспектілерде char түріндегі айнымалылар сәйкес символдардың кодына тең сандар ретінде әрекет етеді.

Атап айтқанда, символдарды үлкен/кіші арқылы салыстыруға болады; Символдар сандар болғандықтан, салыстырулар табиғи түрде жасалады. Символдарды массив индекстері ретінде пайдалануға болады (мысалы, v['$']), олар арқылы циклдар жасауға болады (for (char ch = 'a'; ch <= 'z'; ch++) ) және т.б.

Бірақ char түрінің бір маңызды ерекшелігі бар - ол әдепкіде signed болады, яғни теріс сандарды да сақтай алады. Оның әдепкі диапазоны -128-ден 127-ге дейін. Ascii кестесінің бірінші жартысындағы таңбалардың дұрыс оң кодтары, ал екінші жартысындағы таңбалардың теріс кодтары бар екені анықталды. Бұл жиі кедергі келтіруі мүмкін, бірақ `` unsigned char `` арқылы жұмыс істеу арқылы оңай шешіледі. Мәнді жай ғана `` unsigned char `` түріне көшіруге болады:

char x;
cin >> x;
unsigned char xx = x;  // енді xx 0-ден 255-ке дейінгі дұрыс кодты қамтиды

немесе сіз түрлендіруді пайдалана аласыз, яғни unsigned char түріне нақты түрлендіресіз:

char x;
cin >> x;
v[static_cast<unsigned char>(x)] = ...
// немесе C стиліндегі нұсқа
v[(unsigned char)x] = ...

Жолдар string түріндегі айнымалыларда сақталады, жол тұрақтылары тырнақшаларда көрсетіледі (апострофтарда емес!), кері қиғаш сызық таңбалардан (тырнақшалар және т.б.) шығу үшін қолданылады:

#include <string>

...
string s = "Test";
string s2 = "Quote: \", slash: \\";

Басқа тілдердегідей, жол - элементтері символдардан құралған массив; сәйкесінше, жолмен бірдей әрекеттер массивпен бірге қол жетімді: size, push_back, pop_back, индекс бойынша элементті тік жақша арқылы алу. Бұған қоса, size немесе length (яғни s.size() немесе s.length() жазуға болады) әдісі бар. Жолдарды да қосу қолжетімді (s1 + s2 - s2 жолы тағайындалған s1 жолы).

Сандық айнымалылардан айырмашылығы, егер сіз жол инициализацияламасаңыз, ол автоматты түрде бос жолға инициализацияланады.

Мен сізге енгізу-шығару туралы бөлек айтып беремін. Шығару әдеттегі cout << ... көмегімен жүзеге асырылады. Енгізуді cin >> ... арқылы жасауға болады, бірақ ол бірінші бос орынның алдындағы жолды оқиды (немесе жолды беру). Жол үзілгенге дейін бүкіл жолды оқу үшін getline(cin, s); жазу керек.

Санды жолға түрлендіру to_string командасымен орындалады, мысалы, string s = to_string(x);. Кері түрлендіру қажетті шығыс түріне байланысты stoi (string-to-int) немесе stoll (string-to-long-long) функциялары арқылы орындалады.

Сондай-ақ пайдалы деректер түрі istringstream (кіріс жолының ағыны) туралы бөлек айтамын. Ол кез келген жолды cin ұқсас «енгізу ағынына» айналдыруға, содан кейін >> көмегімен одан сандар мен басқа деректерді «оқуға» мүмкіндік береді. Ол былай жазылған:

#include <sstream>

...

string s = "12 13";
istringstream ss(s);
int a, b;
ss >> a >> b;  // демек a == 12, b == 13

Бұл әсіресе сандарды «жолдың соңына дейін» оқу қажет болғанда пайдалы. Осылайша, мысалы, кіріс деректерінің бір жолын сандар массивіне айналдыруға болады:

string s;
getline(cin, s);
istringstream ss(s);
vector<int> v;
int x;
while (ss >> x) {
    v.push_back(x);
}

Мұнда бейтаныс конструкциялардан-тек while ішіндегі >> енгізу операторын қолдану. Мәселе мынада, кез-келген енгізу операциясын шартта қолдануға болады - бұл енгізудің сәтті болғандығын тексереді. Тиісінше, цикл «ss -тан сандарды оқу мүмкін болғанша» жұмыс істейді. ss ішінде басқа сандар болмаған кезде цикл тоқтайды.

ostringstream (шығару жолының ағыны) симметриялық түрі бар, оған деректерді << арқылы шығарып, содан кейін оны жолға түрлендіруге болады. Бірақ мен бұл туралы егжей-тегжейлі жазбаймын, оның көп қажеті жоқ.

Соңында, массивтер сияқты жолдар да С++ нұсқасында бар және C нұсқасында да бар екенін атап өтемін. С стилінде жол үшін char* немесе char[] деп белгіленетін символдардың «шикі массиві» қолданылады. Оны сіздің бағдарламаларыңызда қолданудың қажеті жоқ.

1.11. Нақты сандар

Жалпы, қазіргі заманғы процессорлар :ref:` нақты сандардың үш түрі <pythonBasicsFloatTypes>` қолдайтынын еске салайын:

  • single — мантиссаның 7-8 цифрын сақтайды, көрсеткіші шамамен ±40 дейін, жадта 4 байт алады, салыстырмалы түрде жылдам жұмыс істейді;

  • double — мантиссаның 15-16 цифрын сақтайды, көрсеткіші шамамен ±300-ге дейін, 8 байтты алады, сәл баяу жұмыс істейді;

  • extended — 19-20 мантисса цифрын сақтайды, көрсеткіш шамамен ±5000 дейін,

жадта 10 байт алады, әлдеқайда баяу жұмыс істейді;

C++ - да single (float деп аталады), double (double деп аталады) түрлері бар, сонымен қатар компиляторға байланысты double немесе extended болуы мүмкін long double түрі бар.

Біздің бағдарламалардың көпшілігінде double немесе long double түрін қолданған жөн; float түріне әдетте біздің тапсырмаларымызда дәлдік жетіспейді. Атап айтқанда, Python-да float double, ал C++ тілінде float single екенін ескеріңіз.

Енгізу-шығару cin/cout арқылы да жұмыс істейді, тек cout әдепкі бойынша санды алты маңызды санға дейін дөңгелектейтінін есте ұстаған жөн. Көбінесе бұл бізге жеткіліксіз, содан кейін бағдарламаның басында, мысалы, cout.precision(20); — бұл 20 маңызды сандарды шығаруды талап етеді. Бұл, әрине, көп, тіпті тым көп, бірақ одан да жаманы болмайды және дәлірек айтсақ, шығару кезінде дәлдікті жоғалтқаннан гөрі жақсы.

Питонмен бірдей мағынада ceil, floor, trunc және round функциялары бар; оларды пайдалану үшін cmath (#include <cmath>) тақырып файлын қосу керек. Модульді (abs) қолдану үшін cmath қосу керек, әйтпесе күтпеген жағдайлар орын алуы мүмкін.

Python<pythonBasicsFloat> туралы мәтіннің сәйкес бөлімінде сипатталған нақты сандармен жұмыс істеу дәлдігі және eps туралы барлық ойлар C++ үшін де жарамды.

1.12. Логикалық деректер түрі

Логикалық деректер түрі bool деп аталады және екі мәнді қабылдай алады: true және false (кіші регистр). Басқа тілдердегі сияқты, салыстыру нәтижелерін және басқа шарттарды тікелей bool түріндегі айнымалыға жазуға болады; және bool түріндегі айнымалы тікелей if, while және т.б. қолдануға болады.

Note

Басқа тілдерден айырмашылығы, bool да бүтін сан түрі. Егер сіз арифметикалық өрнек жазсаңыз, онда false 0 — ге, ал true 1 - ге айналады. Сол сияқты, логикалық операциялар тек true/false ғана емес, сонымен қатар ерікті сандарды да қабылдайды: 0 false болып саналады, ал қалған барлық мәндер true болып табылады.

bool x = 1 + 2;  // 1 + 2 == 3,  true-ға айналады.
int y = x;  // x == true,  1-ге айналады.
int z = x + 10;  // x == true, 1-ге айналады, 1 + 10 == 11.
if (z) {  // if (z != 0) сияқты жұмыс істейді.
}
cout << true << '\n';  // 1-ді шығарады.
cout << false << '\n';  // 0-ді шығарады.
cin >> x;  // жауап ретінде 0 немесе 1 күтеді, басқа сан немесе жолдарды енгізуге болмайды.

Бірақ тұтастай алғанда, бұлай жазудың қажеті жоқ, кейбір жағдайларда бұл байқалмайтын қателіктерге әкелуі мүмкін. Тексерулерді толығымен жазыңыз (z != 0), if-та да, int-ті bool-ға сақтаған кезде де, мұндай жағдайларда да bool - мен арифметикалық амалдарды қолданбаңыз.

1.13. Функциялар

Жалпы функция келесі түрде анықталады:

int foo(int x, double y, string s) {
    ...
}

Бұл үш параметрді қабылдайтын foo функциясы: int түріндегі x, double түріндегі y, s түріндегі string және int түрін қайтарады. Егер аргументтер болмаса, бос жақшаларды жазу керек: int foo() {...}. Функция ішінде return<мән> командасы функцияны тоқтату және мәнді қайтару үшін пайдаланылады.

Функцияны орындаудың кез келген тармағы return<мән> командасымен аяқталуы керек; оның жоқтығы undefined behavior (төменде қараңыз), яғни ол болмаған жағдайда бағдарлама кез келген жолмен әрекет ете алады. (Ерекшелік - void қайтаратын функциялар, төменде қараңыз.)

Ерекше жағдай - ештеңені қайтармайтын функциялар (Паскаль тілінде «процедуралар»). Мұндай функциялар үшін void арнайы қайтарылатын мән түрін көрсету керек:

void foo() {
   ...
}

Тиісінше, мұндай функцияларда тек мәнсіз return қолдануға болады және мұндай функция шақырылған жерде оның нәтижесін ешбір жолмен пайдалану мүмкін емес. Сонымен қатар, функцияның соңында return деп жазудың қажеті жоқ.

Функцияның ішіндегі локалды айнымалылар стандартты жолмен анықталады: қажет болғанда айнымалы мәнді функция кодында жариялайсыз. C++ тілінде Python global сияқты жазба жоқ; керісінше, барлық локалды айнымалылар анық жариялануы керек болғандықтан, егер жарияланбаған айнымалыны пайдалансаңыз, C++ оны глобал айнымалы деп ойлайды (ал олай болмаса, бұл компиляция қатесі болады).

Параметрлерді функцияларға беру Python-дағыдай тривиальды емес. Біріншіден, параметрлерді жоғарыда сипатталғандай жариялауға болады: жай ғана параметрдің түрі мен атауы. Содан кейін, мұндай функция шақырылған кезде, мәндер сәйкес локалды айнымалыларға көшіріледі, яғни жоғарыдағы мысалда x, y және s шақыру кезінде функция аргументтеріне жіберілген мәндердің көшірмелері болады. x, y және s өзгертулері сыртқа көрінбейді. Бұл «мән бойынша өтетін параметр» деп аталады.

Сондай-ақ «сілтеме бойынша» жіберуге болады, ол былай жазылған:

int foo(int& x, double& y, string& s) {
    ...
}

Енді функция шақырылған кезде айнымалылардың көшірмелері жасалмайды, x, y``және ``s бірдей айнымалыны, функция шақырылған кезде берілген жадты көрсетеді. Егер функцияны foo(a, b, c) деп атасақ, онда функцияның ішінде x бірдей айнымалыға, a сияқты жадқа сәйкес келеді, ал x - дегі өзгерістер a- да көрінеді және y және s-те де солай. Әрине, бұл функция шақырылған кезде параметрлерде өрнектер емес, айнымалылар көрсетілуін талап етеді, foo(q + w, b, c) түрінің жазбасы жұмыс істемейді, өйткені q+w айнымалы емес.

Сілтеме арқылы өту функциядан тыс айнымалы мәндердің өзгерістерін шынымен көру қажет болғанда қолданылады, бірақ бұл өте нашар тәжірибе болып саналады (өйткені функция шақырылған жерде айнымалының өзгеретіні анық емес) .

«тұрақты сілтеме» арқылы жіберетін әдіс бар:

int foo(const int& x, const double& y, const string& s) {
    ...
}

Бұл шамамен сілтеме бойынша өтумен бірдей, тек енді бұл айнымалы мәндерді функция ішінде өзгерту мүмкін емес. Осыған байланысты, біріншіден, сырттан ешбір өзгерістер көрінбейді (жай ғана себебі, мүлде өзгерістер болмайды), екіншіден, айнымалыларды ғана емес, foo ішіне өрнектерді беруге болады (foo(q + w, b, c) деп жазуға болады).

Тұрақты сілтеме арқылы беру ең алдымен мәндерді көшірмеу үшін қолданылады. int көшіру ұзаққа созылмайды. Бірақ string немесе string ұзындыңы ұзын болса оны көшіру өте ұзақ болуы мүмкін. Егер сіз тұрақты сілтеме арқылы берсеңіз, онда көшірмелер болмайды. Мысалы, егер сіз графты (іргелес матрица немесе іргелес шыңдардың тізімдері) іздеу түрінің функциясына тереңдікке бергіңіз келсе, онда тұрақты сілтеме арқылы жіберіңіз.

Әрине, нұсқаларды қалағаныңызша біріктіруге болады, параметрлердің бір бөлігін бір жолмен, бір бөлігін басқа жолмен беруге болады:

int foo(int x, double& y, const string& s) {
    ...
}

Жалпы алғанда, кішігірім типтер (ең алдымен қарапайым деректер типтері, массивтер, жолдар немесе басқа күрделі типтер емес) әдетте мән бойынша, ал үлкендері Тұрақты сілтеме арқылы беріледі. Мән бойынша беру әлі де қолданылады, егер сізге функцияда айнымалыны өзгерту қажет болса, бірақ ол сыртынан байқалмайтындай етіп жасаймын десеңіз, оны көшірмесіз жасай алмайсыз.

Тұрақты емес сілтеме арқылы беру айнымалыдағы өзгерістерді сырттан көру қажет болса қолданылады және өте сирек қолданылады.

1.14. Файл бойынша енгізу/шығару

Файл енгізу/шығару пернетақтаның кірісі мен экранға шығуына толығымен ұқсас. Кодтың бас жағына fstream файлын қосу керек (filestream-нан), осыдан кейін енгізу (input file stream) үшін fstream немесе шығару (output file stream) үшін ofstream сияқты объекттіні құрыңыз, жақшаға файл атауын енгізіп, олармен cin және cout ретінде жұмыс істеуді жалғастырыңыз:

#include <fstream>

....

ifstream in("input.txt");
int a, b;
in >> a >> b;

ofstream out("output.txt");
out << a + b;

Сізге деректерді «файлдың соңына дейін» оқу қажет болуы мүмкін. Бұл әрекетті орындау үшін оқудың сәтті болғанын оңай тексеруге болады: әрбір оқу әрекетті if немесе while жағдайында тексеруге болатын кейбір объектті (шын мәнінде бірдей кіріс ағынын) қайтарады. Мысалы, кіріс файлындағы барлық сандарды осылай оқып, олардың сомасын есептеуге болады:

int sum = 0;
int x;
while (in >> x) {  // оқу әзірге сәтті болған кезде
    sum += x;
}

Сонымен қатар, ағын объектілерінде (бұл жағдайда in) eof әдісі бар, ол файлдың таусылғанын айтады және сіз осылай жазғыңыз келуі мүмкін

// бұлай жасауды қажеті жоқ
while (!in.eof()) {
    int x;
    in >> x;
    ...
}

Бірақ ол осылай жұмыс істемейді. Мәселе мынада, файлды енгізу ағыны файлдың оқудың сәтсіз әрекетінен кейін ғана аяқталғанын біледі. Соңғы санды оқығанда in.eof() шарты бәрібір жалған болады. Сіз тағы бір санды санауға тырысасыз, оқу сәтсіз болады, x-де бірдеңе болады (C++11-ден бастап нөлге тең болатынына кепілдік беріледі, бірақ оған сену қиындау), содан кейін ғана in.eof() true қайтарады. Әрине, біздің күткеніміз бұл емес. Санды оқу нәтижесін while (in >> x) немесе сол сияқтыларды пайдаланып тексеру дұрыс.

Сол сияқты, ешқашан while (in) {...} деп қолданбаңыз, өйткені ағынның өзін тексеру сәтсіз оқылғаннан кейін ғана жалған болады.