文章目錄
-前言
上次總結了C語言的知識點,這次總結一下C++的學習心得。C++擴充和完善了C語言,是一門面向對象的編程語言。
-hello world
我們先看看hello world的例子:
#include <iostream>
using namespace std;
int main() {
cout << "hello world!" << endl;
return 0;
}
1.#include <iostream> c++提供給開發者的一些頭文件;
2.using namespace std; 命名空間
-命名空間
C++中命名空間,作爲附加信息來區分不同庫中相同名稱的函數、類、變量等。例如,我們現在有兩個頭文件中都有print()函數,那我們就需要頭文件來區分。
first.h:在頭文件first.h中寫一個print()函數,
#pragma once
#include<iostream>
void print() {
std::cout << "頭文件1" << std::endl;
}
再創建一個頭文件two.h,也實現print()函數(這裏和上面first.h代碼一樣),我們在.cpp中同時引入這兩個頭文件,如下:
#include "first.h"
#include "two.h"
#include <iostream>
using namespace std;
int main() {
print();
return 0;
}
在main函數中調用print()函數。這時候就會有問題,print()到底是調用哪個頭文件下的呢?爲了區分這print(),我們需要使用命名空間。在頭文件first.h中定義命名空間:
namespace ftd {
void print() {
std::cout << "頭文件1" << std::endl;
}
}
在main中使用命名空間
int main() {
ftd::print();
return 0;
}
這裏就可以直接調用first.h中的print()函數了。當然也可以像下邊這樣使用:
using namespace ftd;
print();
-引用變量
引用變量可以理解爲是變量的一個別名。
int a = 1;
int& b = a;
cout << a << endl;
cout << b << endl;
在這裏 a,b打印出來的值是一樣的。這裏需要注意的是引用變量聲明的時候必須初始化。
引用變量作爲參數
void swap(int a,int b) {
a ^= b;
b ^= a;
a ^= b;
}
void swap1(int* a, int* b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void swap2(int& a, int& b) {
a ^= b;
b ^= a;
a ^= b;
}
int main() {
int a = 3;
int b = 4;
swap(a, b);
printf("a , b 的值:%d,%d \n",a,b);
swap1(&a,&b);
printf("a , b 的值:%d,%d \n", a, b);
swap2(a, b);
printf("a , b 的值:%d,%d \n", a, b);
return 0;
}
上面的三個函數 swap,swap1,swap2中第二個和第三個函數分別用指針和引用變量作爲形參。可以發現第一個函數因爲是值傳遞,main中的a,b兩個參數的值並沒有發生變化;第二個使用的指針,因爲指針指向的值發生改變,所以main函數的a,b值換了;第三個傳的是變量本身,因此也可以改變傳入變量本身的值。
引用變量作爲返回值
int getA1() {
static int a = 15;
return a;
}
int& getA2() {
static int a = 10;
return a;
}
int* getA3() {
static int b = 3;
return &b;
}
int main() {
cout << getA1() << endl;
// getA1() = 1;
cout << getA2() << endl;
getA2() = 2;
cout << getA2() << endl;
cout << *getA3() << endl;
*getA3() = 2;
cout << *getA3() << endl;
return 0;
}
首先三個函數中返回的是靜態變量,因爲局部變量在函數執行完會被釋放。其次,我們可以發現函數getA2()和getA3()可以作爲左值被賦值。因爲getA2()返回的是變量,getA3()返回的是指針。getA1()返回的是一個int類型的值,所以getA1()不能作爲左值。
-函數
內聯函數
inline void printA() {
cout << "printA" << endl;
}
int main() {
printA();
return 0;
}
上面 printA() 即內聯函數,在函數的前面聲明inline。內聯函數C++編譯器會將函數裏的代碼嵌入到調用該函數的函數中,減少了函數入棧出棧的開銷。但是聲明瞭inline,編譯器不一定就會讓該函數內聯。我們需要注意以下幾點:
// 1.內聯函數聲明時候必須實現,不能分開。分開的話,編譯器不會內聯;
// 2.內聯函數必須在調用函數的前面;
// 3.內聯函數中不能存在任何形式的循環,不然編譯器不會內聯;
// 4.內聯函數中不能存在過多的條件語句,不然編譯器不會內聯;
// 5.內聯函數不能過於龐大;
// 6.內聯函數不能進行取地址操作;
必須遵循以上幾點,我們聲明的內聯函數編譯器纔可能允許內聯。還有一點有的沒有聲明內聯的函數編譯器也可能將其內聯。
默認參數
//默認參數
void print(int i=4) {
cout << i << endl;
}
int main() {
print();
print(2);
return 0;
}
在C++中形參是可以設置默認值的,像上面一樣,print()中 i 打印出來是 4 ;print(2)中 i 打印的是2。也就是如果函數傳參了,用傳的值,否則用默認值。
//默認參數
void print(int a,int b=4) {
cout <<"a:"<<a<<"b:"<<b<< endl;
}
void print1(int a, int b = 4,int c) { //這裏是錯誤的
cout << "a:" << a << "b:" << b << endl;
}
int main() {
print(1);
print(1,2);
return 0;
}
print1()函數是報錯的,當參數中有默認值出現嗎,那麼後面的參數也必須有默認值。
-類
類和類函數的聲明一般在頭文件中,
class First
{
private:
int a;
public:
void setA(int a);
int getA();
};
函數的實現在cpp中:
void First::setA(int a) {
this->a= a;
}
int First::getA() {
return a;
}
類的使用:
int main() {
First first;
first.setA(2);
cout << first.getA() << endl;
return 0;
}
和java中是不是有點相似。
構造函數
class Test
{
public:
Test() {
}
Test(int a) {
}
Test(int a,int b) {
}
Test(const Test& test) {
}
private:
};
以上便是C++中的構造函數,基本和java相似。最後一個構造函數是拷貝構造函數,也是構造函數的一種。
int main() {
//1.調用無參構造函數
Test test;
//2.
Test test0();
//3.調用有參構造函數
Test test1(1,2);
//4.
Test test2 = 1;
//5.這種調用,調用一個參數的構造函數
Test test3 = (1, 2);
//6.手動調用構造函數
Test test4 = Test(1);
//7.調用的拷貝構造函數
Test test5 = test1;
//8.
Test test6(test1);
return 0;
}
以上是幾種類聲明的方法。這裏注意幾點:
- 當類中沒有定義構造函數,系統會提供一個默認的無參構造函數;當類中有構造函數,系統將不提供無參構造函數。(和java中不同);
- 註釋 5 :無論傳多少個參數,都是使用的最後一位的參數去調用的一個參數的構造函數;
- 註釋 7 8 : 調用的是拷貝構造函數;
- 形參也會調用拷貝構造函數 (如果類中沒有拷貝函數,則調用系統中的);
- 構造函數中不能再調用別的構造函數;
析構函數
~Test(){
}
這就是析構函數,構造函數前面有一個~標識符。在該對象釋放的時候調用,用於釋放類內的資源。
初始化列表
class A
{
public:
A(int a) {
this->a = a;
cout << a << endl;
}
~A() {
}
private:
int a;
};
class B {
public :
B(int c):a1(1),a2(c){
}
private:
int b;
A a1;
A a2;
};
int main() {
B b(3);
return 0;
}
像上面代碼,B類中含有A類對象並且A類沒有無參構造函數的時候,在構造B類的時候需要初始化A類。如上 className() : A類變量名(參數值),A類變量名(參數值);並且這些A類變量的初始化順序是和他們聲明時的順序一致。
初始化列表的使用場景:
- 成員變量是一個類類型,而且類中只要有參數的構造函數;
- const 變量
- 初始化父類的構造函數( 父類出現在初始化列表時,優先父類初始化再按初始化順序初始化別的類成員)
對象的動態創建和釋放
在棧上創建/釋放對象和堆上創建/釋放對象的區別:
- 在棧上創建的對象,創建後大小無法改變 (堆上可以動態調整);
- 棧上創建的對象,系統自動創建和銷燬 (堆上申請的空間必須手動申請和釋放);
堆上申請空間/釋放空間方法:c語言:malloc/calloc free; c++: new/delete new[]/delete[];
//new 分配內存
int* p2 = new int(0);
cout << *p2 << endl;
*p2 = 10;
cout << *p2 << endl;
delete p2;
p2 = nullptr;
//new int[]
int* p3 = new int[10];
p3[2] = 2;
cout << p3[2] << endl;
delete[] p3;
p3 = nullptr;
這裏注意:
1. 我們可以直接 new int(10) 初始int值爲10;
2. new 申請的內存 delete 釋放;new[]申請的內存 delete[]釋放;
class A
{
public:
A(int a) {
}
~A();
}
int main() {
A* a = new A(10);
return 0;
}
new 爲複雜對象申請內存也可以直接設置默認值。
-友元函數/友元類
友元函數
友元函數:定義在類的外部且函數內部的類對象可以直接操作該對象的private/protected屬性和函數。
友元函數的聲明也很簡單,在函數的前面加上 friend 即可。如下:
class A{
public:
A(int c) {
}
private:
friend void test(A a);
void print() {
cout << "友元調用" << endl;
}
int c;
};
void test(A testA) {
testA.print();
cout << testA.c << endl;
}
int main() {
return 0;
}
上面的代碼中:函數test()中的testA參數雖然是A類型的,但是是不可以使用A類中私有的 c元素和print()函數的。
但是,我們在A類中通過這行代碼 friend void test(A a) 將這個函數聲明爲友元函數就可以了。
這裏需要注意的是:友元函數的聲明在public/private/protected中都可以。
友元類
友元類:假設在A類中聲明瞭友元類B,則在B類中可以使用A類中任何類型的元素和函數。
友元類的聲明:friend class 類名;
class A
{
public:
A() {}
private:
friend class B;
void print() {
cout << number << endl;
}
int number;
};
class B
{
public:
B() {}
private:
void print() {
testA.number;
}
A testA;
};
我們可以發現,在A中聲明的友元類B中的testA對象可以直接使用number元素。
-繼承
當創建一個類時,您不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱爲基類,新建的類稱爲派生類。
在C++中,一個派生類可以派生很多基類(就是可以有很多父類)。派生列表格式如下:
class derived-class: access-specifier base-class
derived-class:派生類的名稱;base-class 基類的名稱;access-specifier 繼承類型。
class parent
{
public:
parent() {}
int number1;
private:
int number;
};
class child : public parent
{
public:
child() {
}
private:
};
這裏注意:
- 派生類中可以訪問除了private修飾的所有基類的成員。
- 派生類繼承所有基類函數,除了:構造函數,析構函數,拷貝構造函數,重載運算符 和 友元函數。
- 基類的構造函數,派生類用初始化列表調用;
- 類的繼承類型按以下規則:
- public 繼承基類的public 和 protected類型在派生類中仍是public 和 protected;
- protected 繼承基類 public 和 protected類型變成派生類中的 protected類型;
- private 繼承基類 public 和 protected類型變成派生類中的 private 類型;
類 | public | protected | private |
---|---|---|---|
同一個類 | yes | yes | yes |
派生類 | yes | yes | no |
外部的類 (public繼承) | yes | no | no |
外部的類 (protected繼承) | no | no | no |
外部的類 ( private繼承) | no | no | no |
-抽象類
class Shap
{
public:
virtual void area()=0;
};
class Circle:public Shap{
public:
Circle(int a) :a(a) {
}
void area() {
cout << 3.14 * a * a << endl;
}
private:
int a;
};
int main() {
Shap* shap = new Circle(2);
shap->area();
return 0;
}
C++ 接口是使用抽象類來實現的,如果類中至少有一個函數被聲明爲純虛函數,則這個類就是抽象類。純虛函數是通過在聲明中使用 “= 0” 來指定的,上文中的Shap中的area()函數就是純虛函數,並且Shap類不能被實例化。
-模板
template <typename T>
T const Max(T a,T b) {
return a < b ? b : a;
}
int main() {
int a = 2;
int b = 3;
cout << Max(3,5) << endl;
cout << Max(1.2,2.3) << endl;
return 0;
}
上面代碼中使用的是c++中的模板,和java中的泛型有點類似。Max方法可以比較 int double的值的大小。一般模板函數的寫法如下:
template <typename type>
ret-type func-name( list)
{
// 函數的主體
}
type: type 是函數所使用的數據類型的佔位符名稱,由我們自定義;
ret-type:函數返回類型;
func-name:函數名稱;
類模板的聲明如下:
template <class type> class class-name {
}