Metodyki

i

techniki programowania

 

  1. przekazywanie argumentów do funkcji
  2. referencje
  3. const
  4. argumenty domyślne funkcji
  5. foreach
#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main(){

    vector<char> v;

    v.push_back('A');
    v.push_back('l');
    v.push_back('a');
    v.push_back(' ');
    v.push_back('m');
    v.push_back('a');

    string s; 
    s=v;
    cout << s << endl;

    return 0;
}

Zagadka

Co w poniższym kodzie jest źle?

#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main(){

    vector<char> v; 
    v.push_back('A');
    v.push_back('l');
    v.push_back('a');
    v.push_back(' ');
    v.push_back('m');
    v.push_back('a'); // przechowujemy znaki w kolejnych elementach
    
    string s; // przechowujemy znaki w stringu
    s=v; // błąd kompilacji, dlaczego?
    cout << s << endl;

    return 0;
}

Zagadka

Co w poniższym kodzie jest źle?

#include <iostream>
#include <vector>

using namespace std;

string convert(std::vector<char> v)
{
// Tworzymy zmienna s typu string od razu wypełniając ją zawartoscią wektora.
// Żeby to zrobić bierzemy dwa iteratory (na początek i koniec zmiennej v) 
    std::string s(v.begin(), v.end()); 
    return s;
}

int main(){
    vector<char> v;
    string s;

    v.push_back('A');
    v.push_back('l');
    v.push_back('a');
    v.push_back(' ');
    v.push_back('m');
    v.push_back('a');

    s=convert(v);
    cout << s << endl;

    return 0;
}
#include <iostream>

using namespace std;
     
// funkcja modyfikuje lokalną kopię, a nie obiekt, który przekazujemy
void fun(int argument)   
{
    argument++;
}  
          
int main()
{
    int q=20; //q ma wartość 20      
    fun(q); 
    cout << q << endl; // q w dalszym ciągu 20

    return 0;
}

Argumenty do funkcji

zawsze przekazywane przez kopię

#include <iostream>
#include <vector>

using namespace std;
     
// funkcja modyfikuje lokalną kopię, a nie obiekt, który przekazujemy
int fun(vector<int> arg)   
{
    int sum=0;
    for(auto elem : arg){
       sum+=elem; 
   }
    return sum;
}  
          
int main()
{
    vector<int> v;
    for(int i=0;i<10000;i++){
        v.push_back(i);
    }

    fun(v); // w momencie wywołania do zmiennej arg (w funkcji) kopiowane są wartości
            // ze zmiennej v, co jest kosztowne i nie jest wydajne

    return 0;
}

Argumenty do funkcji

zawsze przekazywane przez kopię

#include <iostream>

using namespace std;

int main()
{
    int a=15;
    a++;           //a=a+1  
    int & rf=a;    // od tego momentu: kiedy piszę "rf", to mam na myśli "a"
    rf++;          

    cout << a << endl; //na ekranie wypisze się 17

    return 0;
}

Typy referencyjne

  • Obiekty typów referencyjnych nie posiadają własnego osobnego adresu.
  • Same nie zajmują miejsca w pamięci.
  • Są tylko aliasami, przezwiskami na zmienne innych typów.
  • Raz przypisanej referencji nie można przenieść na inny obiekt.
  • Zatem nie można również stworzyć referencji, która nie wskazuje na nic.
#include <iostream>

using namespace std;

int main()
{
    int a=15;
    a++;           //a=a+1  
    int & rf=a;    // od tego momentu: kiedy piszę "rf", to mam na myśli "a"
    rf++;          

    cout << a << endl; //na ekranie wypisze się 17

    return 0;
}

Typy referencyjne

sposób zapisu referencji:

nazwa_typu & nazwa_zmiennej

#include <iostream> 

using namespace std;

int main()
{
    int a=15, b=12;
    a=a+1; // zwiększam wartosć zmiennej
    int & rf=a; // tworzę referencję (alias) do a
    rf++; // zwiększam poprzez alias obiekt pierwotny a

    cout << a << endl; //17
    cout << b << endl; //12
    cout << rf << endl; //17 (bo wskazuje na a)

    rf=b;  //alias NIE przenosi się na inną zmienną, 
           // zamiast tego przypisujemy do obiektu a (bo na niego wskazujemy) 
           // wartosć zmiennej b

    cout << a << endl; //12
    cout << b << endl; //12
    cout << rf << endl; //12 (bo wskazuje na a)

    rf++;

    cout << a << endl; //13
    cout << b << endl; //12
    cout << rf << endl; //13 (bo wskazuje na a)

    return 0;
}



#include <iostream> 

using namespace std;


// argument jest dalej przekazywany przez kopię
// ale tym argumentem jest alias na inną zmienną, więc nawet jak skopiujemy alias
// to i tak dalej wskazujemy na obiekt, który jest na zewnątrz
void fun(int & v)
{
    v++; // modyfikujemy obiekt i będzie to oddziaływało na zmienną 
         // poza ciałem funkcji (na zewnątrz funkcji)
}

int main()
{
    int a=1000;
    fun(a);
    cout << a << endl; // 1001

    int & ref_a = a;
    fun(ref_a);
    cout << ref_a << endl; // 1002

    return 0;
}



#include <iostream>

using namespace std;

void fun(int & aa, int & bb, int & cc, ind & dd)      
{
    aa++;
    bb=100;
    cc=200;
    dd=bb+cc;
}

int main()
{
    int a=15, b=12, c, d; // wartosc c i d nieokreslona   
    
    cout << a << endl; // 15
    cout << b << endl; // 12
    cout << c << endl; // dowolna liczba (może -23453632, a może 235689135, a może 1000
    cout << d << endl; // dowolna liczba (może -757654, a może 54523098, a może -658765

    fun(a,b,c,d); 
    
    cout << a << endl; // 16 
    cout << b << endl; // 100 
    cout << c << endl; // 200
    cout << d << endl; // 300

    return 0;
}

Zastosowania

Zwracanie większej liczby parametrów z funkcji

#include <vector>
#include <iostream>

using namespace std;

// agument jest zawsze przekazywany przez kopię
// dlatego też nie modyfikujemy oryginalnego obiektu
// argument może nazywać się jakkolwiek, nawet tak samo jak w main: v !
// to jest inna zmienna/obiekt/byt!
int fun(vector<int> arg)
{
    for(int i=0;i<10;i++){
        arg.push_back(i);
    }

    return arg.size();
}

int main()
{
    vector<int> v;
    for(int i=0;i<10;i++){
        v.push_back(i);
    }

    cout << fun(v) << endl; // wyswietli 20
    cout << v.size() << endl; // wyswietli 20

    return 0;
}

Używanie referencji do przekazywania argumentów funkcji

#include <vector>
#include <iostream>

using namespace std;

int fun(vector<int> & arg) // tu zmiana na typ refencyjny!
{
    for(int i=0;i<10;i++){
        arg.push_back(i);
    }

    return arg.size();
}

int main()
{
    vector<int> v;
    for(int i=0;i<10;i++){
        v.push_back(i);
    }

    cout << fun(v) << endl; // wyswietli 20
    cout << v.size() << endl; // wyswietli 20

    return 0;
}

Używanie referencji do przekazywania argumentów funkcji

int main(){
    int a=100;
    a++; // poprawne, w pełni dozwolone

    const int b=20; // to jest poprawne, tworzymy obiekt, którego obiecujemy nie modyfikować
                    // ale w momencie tworzenia przypisujemy mu wartość początkową

    b++; // błąd kompilacji, teraz już tego obiektu nie wolno modyfikować

    
    const int c=a; // poprawne, tworzymy nowy stały obiekt, do którego przypisujemy wartość
    const int d=b; // też poprawne (tworzymy nową zmienną, 
                   // której wartość już się nie zmieni 
                   // i przypisujemy jej wartość (kopiujemy ją)!

    d=b; // błąd kompilacji (tu próbujemy zmienić wartość w trakcie życia zmiennej, 
         // a wcześniej obiecywaliśmy nie zmieniać!
    
    return 0;
}

Słowo kluczowe const

Dodając const tworzymy nowy typ. stały typ nazwa_typu (np. stały int). Czyli gwarantujemy, że obiekcie takiego typu nie można wykonać operacji, która zmieni jego wartość. Dzięki temu kompilator pomaga sprawdzić poprawność kodu.

void fun(int arg){
    arg++; // poprawne
}

void fun(const int arg){
    arg++; // błąd kompilacji, próbujemy zmodyfikować zmienną arg, 
           // a obiecaliśmy, że tego nie będziemy robili!
}

int main(){
    int a;
    a=7;
    const int b=4; // poprawne, przypisujemy początkową wartość
                   // zmiennej typu const od razu w momencie tworzenia

    fun(3); // poprawne, wewnątrz funkcji wartość zmiennej arg jest kopiowana z liczby 3
    fun(a); // poprawne, wewnątrz funkcji wartość zmiennej arg jest kopiowana ze zmiennej a
    fun(b); // poprawne, wewnątrz funkcji wartość zmiennej arg jest kopiowana ze zmiennej b
    return 0;
}

Słowo kluczowe const

Dodając const tworzymy nowy typ. stały typ nazwa_typu (np. stały int). Czyli gwarantujemy, że obiekcie takiego typu nie można wykonać operacji, która zmieni jego wartość. Dzięki temu kompilator pomaga sprawdzić poprawność kodu.

#include <iostream>
#include <vector>
#include <string>


int main() {
    int a=10, b=20;
    const int & ref=a;
    a++;

    // błąd kompilacji, próbujemy zmienić obiekt przez alias, 
    //który nie ma do tego prawa
    ref++;
  
    cout << ref << endl; // wolno, cout nie modyfikuje obiektu ref, wynik 11
       
    ref=b; // możemy przepisać referencję na inny obiekt
    cout << ref << endl; // wolno, cout nie modyfikuje obiektu ref, wynik 20
    return 0;
}

Stałe typy referencyjne: CONST &

Analogicznie, dodając const przy typie referencyjnym, mówimy o stałym typie referencyjnym. Obiekty tego typu to aliasy, które gwarantują, że nie zmodyfikują obiektów, na które w rzeczywistości wskazują.

 

int main(){
    int ref &=20; // błąd kompilacji! inaczej, ref2++ zmieniłby stałą 20
    const int & ref=30; // dopuszczalne, obiecujemy, że nie zmienimy stałej 30

    int a=10;
    const int b=20;

    int & ref_a = a; // dopuszczalne, a nie jest typem const int
    int & ref_b = b; // błąd kompilacji! inaczej ref_b++ zmieniłby stałą b

    const int & const_ref_a = a; // obiecujemy, że przez const_ref_a nie zmienimy a
    const_ref_a++; // błąd kompilacji! obiecalismy

    const int & const_ref_b = b; // również ok, niczego nie zmieniamy

    return 0;
}

Stałe typy referencyjne: CONST &

więcej przykładów...

 

int fun_ref(int & ref){
    return ref+100;
}

int main(){
    const int & ref=30;

    int a=10;
    int & ref_a = a;
    const int & const_ref_a = a;

    const int b=20;
    const int & const_ref_b = b;

    fun_ref(a); // referencja do zmiennej dozwolona
    fun_ref(ref_a); // referencja do referencji ok, bo i tak wskaże na docelowy obiekt
    fun_ref(const_ref_a);// błąd! Referencja w funkcji nie jest const, a argument jest const.
    fun_ref(b); // błąd! Referencja w funkcji nie jest const, a argument jest const.
    fun_ref(const_ref_b); // błąd! Referencja w funkcji nie jest const, a argument jest const.

    return 0;
}

Stałe typy referencyjne: CONST &

więcej przykładów...

 

// argumenty CONST referencyjne do typów wbudowanych (int, float i podobne) NIE MAJĄ SENSU!
// W 99% są błędem. Taki kod działa z reguły po kompilacji wolniej.
// Dlaczego? Zostanie wyjaśnione przy wykładzie o wskaźnikach
// Tutaj przykład poglądowy, który ma pokazać kilka konsekwencji referencji.

string const_fun_ref(const string & ref){ // doszedł const!
    return ref+" wyraz";
}

int main(){
    const string & ref="wartosc zmiennej";

    string a="dziesiec";
    string & ref_a = a;
    const string & const_ref_a = a;

    const string b="dwadziescia";
    const string & const_ref_b = b;

    const_fun_ref(a); 
    const_fun_ref(ref_a); 
    const_fun_ref(const_ref_a); // jest ok, argument const, przekazywany obiekt też const
    const_fun_ref(b); // jest ok, argument const, przekazywany obiekt też const
    const_fun_ref(const_ref_b); // jest ok, argument const, przekazywany obiekt też const

    return 0;
}

Stałe typy referencyjne: CONST &

więcej przykładów (z ważną uwagą w komentarzu!)...

 

bool
char
int
long
float
double

Dygresja: typy wbudowane

Typy, które zajmują "mało miejsca" i ich kopiowanie jest tanie. Poniżej bardzo uproszczona lista typów wbudowanych.

Więcej: http://en.cppreference.com/w/cpp/language/types

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() {
    
    vector<string> vs;
    vs.push_back("ala");

    const vector<string> & const_vs=vs; // stały alias na pierwszy wektor
    const_vs.push_back("ma"); // błąd kompilacji! push_back modyfikuje wektor
                              // dokładniej - nie jest metodą const

    vector<string> vs2;
    vs2.push_back("kota");

   const vector<string> & const_vs=vs2; 

    cout << const_vs[1] << endl; // poprawne - wypisywanie nie modyfikuje obiektu
    return 0;
}

Stałe typy referencyjne: CONST &

więcej przykładów...

 

// niezbędne includey

int fun(vector<int> arg){
    int sum=0;
    for(const auto elem : arg){
        sum+=elem;
    }
    return sum;
}

int ref_fun(vector<int> & arg){
    int sum=0;
    for(const auto elem : arg){
        sum+=elem;
    }
    return sum;
}

int const_ref_fun(const vector<int> & arg){
    int sum=0;
    for(const auto elem : arg){
        sum+=elem;
    }
    return sum;
}

Stałe typy referencyjne: CONST &

Podstawowe i najważniejsze zastosowanie const & znajduje w przypadku argumentów funkcji, gdy są nimi duże obiekty (wszystkie inne niż wbudowane).

 

int main(){
    vector<int> v;
    for(int i=0;i<10000;i++){
        v.push_back(i);
    }

    // niepoprawne, niepotrzebna kopia
    cout << fun(v) << endl;
 
    // prawie... ref_fun nie modyfikuje
    // zmiennej v, więc powinna być const
    cout << ref_fun(v) << endl;

    // prawidłowe! kompilator może teraz 
    // zoptymalizować poprawnie kod!
    cout << const_ref_fun(v) << endl;


    return 0;
}
int fun(int argument=3)
{
    argument++;
    return argument;
}

int main()
{
    fun(); // wywołanie fun(3)
    return 0;
}

Argumenty domyślne funkcji

int fun(int arg, int arg2=3){
}
   

int main()
{
    fun(2);  //wywołanie jak fun(2,3)
    return 0;
}

Argumenty domyślne funkcji

//błąd wieloznaczności

int fun(int arg){
    arg++;
    return arg;
}

int fun(int arg, int arg=5)
{
    arg++;
    return arg;
}

int main()
{
    fun(2); //błąd! kompilator nie wie, czy wywołać fun(2), czy fun(2,5)
    return 0;
}

Argumenty domyślne funkcji

#include <iostream>
#include <vector>

using namespace std;
 
int main() 
{
    std::vector<int> v = {0, 1, 2, 3, 4, 5};
 
    for(const int &i : v)
        std::cout << i << ' ';
    cout << endl;
 
    for(auto i: v) 
        std::cout << i << ' ';
    cout << endl;
 
    for(auto&& i: v)
        std::cout << i << ' ';
    cout << endl;
 
    for(int n: {0, 1, 2, 3, 4, 5}) 
        std::cout << n << ' ';
    cout << endl;
 
    int a[] = {0, 1, 2, 3, 4, 5};
    for(int n: a)
        std::cout << n << ' ';
    cout << endl;
    return 0;
}

Nowy sposób iterowania pętli (od C++11)

#include <vector>
#include <set>
#include <iostream>

using namespace std;

// z punktu widzenia użytkownika czytelniejszy
// wiadomo, że idziemy przez wszystkie elementy
// pasuje też do iteratorów (więc można podmieniać obiekty i dalej działa)
int main()
{
    vector <int> v={1,1,1,2,2,2,3,3,4,5,6};
    set<int> s=v;

    //stary sposób
    for(int i=0; i<v.size(); i++){
        s.insert(v[i]);
    }
   
    //nowy sposób - pętla for each
    for(int element : v){
        s.insert(element);
    }
    for(int elem : s){
        cout << elem << endl;
    }
    return 0;
}

Nowy sposób iterowania pętli (od C++11)

#include <vector>
#include <string>
#include <set>

using namespace std;

int main()
{
    vector <string> v={"ala","ma","kota"};
    set <string> s;

    int i=0;
    for(const auto & el:v) //auto, czyli przy kompilacji kompilator ma to zrobić sam
    {
        s.insert(el); //"tu masz wektor, wiesz jaki typ tam się trzyma, więc domyśl się"
        i++; // gdybysmy potrzebowali indeksu                 
    }
    return 0;
}

Wzmianka o auto

Typy referencyjne oraz const

By pedzimaz

Typy referencyjne oraz const

  • 1,091