Metodyki
i
techniki programowania
- przekazywanie argumentów do funkcji
- referencje
- const
- argumenty domyślne funkcji
- 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
są 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
są 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.
#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