需要更好的閱讀的體驗請移步 👉 小牛肉的個人博客 👈
文章目錄
一、數組
1. 數組的存儲與初始化
一維數組
二維數組
2. 數組作爲函數參數
3. 對象數組
對象數組——線性迴歸方程實例
class Point{
public:
Point(float xx = 0,float yy = 0):x(xx),y(yy){ }
float getX() const {return x;}
float getY() const {return y;}
private:
float x,y;
};
//直線線性擬合,number爲點數
float lineFit(const Point points[], int number){
float avgx = 0, avgy = 0;
float lxx = 0,lyy = 0, lxy = 0;
for(int i =0;i<number;i++){
avgx += points[i].getX()/number;
avgy += points[i].getY()/number;
}
for(int i = 0;i<number;i++){
lxy += (points[i].getX() - avgx) * (points[i].getY() - avgy);
lyy += (points[i].getY() - avgy) * (points[i].getY() - avgy);
lxx += (points[i].getX() - avgx) * (points[i].getX() - avgx);
}
cout<<"This line can be fitted by y=ax+b."<<endl;
//輸出迴歸係數a,b
cout<<"a = "<<lxy/lxx<<" "<<"b = "<<avgy-lxy*avgx/lxx<<endl;
return (float)(lxy/sqrt(lxx*lyy)); //線性相關的密切程度
}
int main(){
Point points[10] = {Point(6,10),Point(14,20),Point(26,30),Point(33,40),
Point(46,50),Point(54,60),Point(67,70),Point(75,80),
Point(84,90),Point(100,100)};
float r = lineFit(points,10);
cout<<"Line coefficient r = "<<r<<endl;
}
二、指針
1. 指針運算符 * 和 &
*
稱爲指針運算符,也稱解析;&
稱爲取地址運算符;
2. 指針變量的賦值運算
3. 指針的類型
指向常量的指針 const int * p
*
在const的右邊
指針類型的常量 int * const p
*
在const的左邊
4. 指針的算法運算、關係運算
5. 用指針訪問數組元素
6. 指針數組 Point *p[2]
7. 指針與函數
指針作爲函數參數
爲什麼需要用指針做參數?
- 需要數據雙向傳遞時(引用也可以達到此效果);
用指針作爲函數的參數,可以使被調函數通過形參指針存取主調函數中實參指針指向的數據,實現數據的雙向傳遞 - 需要傳遞一組數據,只傳首地址運行效率比較高 ;
實參是數組名時,形參可以是指針
引用和指針有何區別?何時只能用指針而不能使用引用
- 引用是一個別名,不能爲空,不能被重新分配;指針是一個存放地址的變量。
- 當需要對變量重新賦以另外的地址或賦值爲NULL時只能使用指針
指針類型的函數
若函數的返回值是指針,該函數就是指針類型的函數
int * function(){
}
函數類型的指針
即指向函數的指針
定義:數據類型 (* 函數指針名)(形參表)
int (*func)(int, int)
含義:函數指針指向的是程序代碼存儲區。
#include <iostream>
using namespace std;
int compute(int a, int b, int (*func)(int, int))
{
return func(a, b);
}
int max(int a, int b) // 求最大值
{11
return ((a > b) ? a : b);
}
int min(int a, int b) // 求最小值
{
return ((a < b) ? a : b);
}
int sum(int a, int b) // 求和
{
return a + b;
}
int main()
{
int a, b, res;
cout << "請輸入整數a:";
cin >> a;
cout << "請輸入整數b:";
cin >> b;
res = compute(a, b, &max);
cout << "Max of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &min);
cout << "Min of " << a << " and " << b << " is " << res << endl;
res = compute(a, b, &sum);
cout << "Sum of " << a << " and " << b << " is " << res << endl;
}
8. 對象指針
定義形式:類名 *對象指針名
Point *ptr;
Point a(5,10);
ptr = &a;
對象指針通過 ->
訪問對象成員
ptr->getx()
相當於 (*ptr).getx();
9. this 指針
10. 動態內存分配
首先我們需要分清幾個概念
int *point = new int(5); //用5初始化
int *point = new int(); //用0初始化
int *point = new int; //不分配初值
int *point = new int[5] //表示爲該指針分配包含十個元素的地址空間
同一個程序有可能會處理很大的數據,有時處理很小,若總是以最大的空間內存分配,難免會造成浪費。
- 在C語言中,用
malloc
進行動態內存分配; - 在C++中,用
new
語句
new 動態分配
動態分配一個變量
p = new T; //T代表int,double,float等等
//p類型爲T*的指針
//該語句動態分配了一片大小爲 sizeof(T) 字節的內存空間,並將起始地址賦給了p
int *pn;
pn = new int;
*pn = 5; //往剛開闢的空間中寫入了數據5
動態分配一個數組
p = new T[N];
//動態分配了一片大小爲 sizeof(T)*N 字節的內存空間,並將起始地址賦給了p
int *pn;
int i = 5;
pn = new int[i*20]; //100個元素,最大合法下標爲99
pn[0] = 20;
pn[100] = 30; // 編譯沒問題,運行時數組越界
注:new運算符返回值類型都是 T*
delete 釋放動態分配的內存
delete總是和new成對出現 :即動態分配的內存一定要釋放掉,否則佔用的內存就會越來越多。
delete指針
該指針必須指向new出來的空間
int *p = new int;
*p = 5;
delete p;
delete p; //wrong; 同一片空間不能被delete兩次
delete [ ]指針
int *p = new int[20];
p[0] = 1;
delete []p; //若delete p 則只刪除了一部分,沒有刪乾淨
三、自定義動態數組類 ArrofPoints
將數組的建立和刪除過程封裝起來,數組下標越界檢查
四、Vector類模板(C++標準庫中的)
爲什麼需要vector
- 封裝任何類型的動態數組,自動創建和刪除
- 數組下標越界檢查
vector對象的定義
vector<元素類型> 數組對象名(數組長度)
vector<int> arr(5)
建立大小爲5的int數組vector<int> arr(5,2)
大小爲5的int類型數組,所有元素用2初始化
vector對象的使用
- 對數組元素的引用 :
與普通數組具有相同形式: vector對象名 [ 下標表達式 ]
vector數組對象名不表示數組首地址 ,因爲數組對象不是數組,而是封裝了數組的對象 - 獲得數組長度 :用size函數 數組對象名.size()
vector應用舉例:
五、深複製與淺複製
以上面的自定義動態數組類 ArrayOfPoints 爲例
淺複製
ArrayOfPoints pointsArray2(pointsArray1); //創建副本
- pointsArray1和pointsArray2有相同的值,表面上好像完成了複製,但是指向的是同一個內存空間,並沒有形成真正的副本,當程序中改變pointsArray1時也會改變pointsArray2。
- 而且,在程序結束之前,pointsArray1和pointsArray2的析構函數會自動調用,動態分配的內存空間被釋放,由於兩個對象給公用了同一塊內存空間,因此該空間被釋放兩次,導致運行錯誤。
深複製(重新編寫複製構造函數)
核心思想:重新new一個存儲空間進行值的複製操作
六、字符串
詳見第一章 【C++複習總結回顧】—— 【一】基礎知識+字符串/string類
七、手擼String類
設計一個字符串類MyString,具有構造函數、析構函數、複製構造函數、並重載運算符 +、=、+=、[ ],儘可能的完善它,使之能滿足各種需要。
#include<iostream>
#include<cstring>
using namespace std;
class Mystring{
private:
char* mystring; //字符指針
int len; //字符串長度
Mystring(int clen){ //私有構造,防止其他類進行調用創建實例,只使用一次(在+重載)
mystring = new char[clen + 1];
for (int i = 0; i < clen; i++)
mystring[i] = '\0';
len = clen;
}
public:
Mystring(); //無參構造
Mystring(const char* const cmystring); //帶參構造
Mystring(const Mystring& rhs); //複製構造
~Mystring(); //析構
int getLen() const{ //獲取長度
return this->len;
}
const char *GetMyString() const{ //獲取字符串
return mystring;
}
char & operator[](int offset); //重載下標運算符,作爲左值可被修改,需返回引用
char operator [](int offset) const; //重載下標運算符,作爲右值僅能訪問
Mystring operator +(const Mystring& rhs); //a+b a的值並不會被修改,不需要返回引用
void operator +=(const Mystring& rhs); //a+=b 無返回值
Mystring& operator =(const Mystring& rhs); //a=b a的值會被修改,需要返回引用
};
//無參構造
Mystring::Mystring(){
mystring = new char[1];
mystring[0] = '\0';
len = 0;
}
//帶參構造
Mystring::Mystring(const char* const cmystring){
len = strlen(cmystring);
mystring = new char[len+1];
for(int i = 0; i<len; i++)
mystring[i] = cmystring[i];
mystring[len] = '\0';
}
//複製構造
Mystring::Mystring(const Mystring& rhs){
len = rhs.getLen();
mystring = new char[len+1];
for(int i = 0; i<len;i++)
mystring[i] = rhs[i];
mystring[len] = '\0';
}
//析構
Mystring::~Mystring(){
delete[] mystring;
len = 0;
}
char& Mystring::operator[](int offset){
if(offset>len) //若超出最大長度,返回最後一位字符
return mystring[len-1];
else
return mystring[offset];
}
char Mystring::operator[](int offset) const
{
if (offset > len) //若超出最大長度,返回最後一位字符
return mystring[len - 1];
else
return mystring[offset];
}
//字符串連接+重載
Mystring Mystring :: operator +(const Mystring& rhs){
int totallen = len + rhs.getLen();
Mystring str(totallen);
int i = 0;
for(;i<len;i++)
str[i] = mystring[i];
for(int j = 0;j<rhs.getLen();i++,j++)
str[i] = rhs[j];
str[totallen] = '\0';
return str;
}
void Mystring::operator+=(const Mystring& rhs){
int totallen = len + rhs.getLen();
Mystring str(totallen);
int i = 0;
for(;i<len;i++)
str[i] = mystring[i];
for (int j = 0; j < rhs.getLen(); i++, j++)
str[i] = rhs[j];
str[totallen] = '\0';
*this = str;
}
Mystring &Mystring ::operator=(const Mystring &rhs)
{
delete[] mystring;
len = rhs.getLen();
mystring = new char[len + 1];
for (int i = 0; i < len; i++)
mystring[i] = rhs[i];
mystring[len] = '\0';
return *this;
}
測試:
int main()
{
Mystring s1("initial test");
cout << "S1:\t" << s1.GetMyString() << endl;
char *temp = "Hello World";
s1 = temp;
cout << "Now,S1 is replaced by Hello World" << endl;
cout << "S1:\t" << s1.GetMyString() << endl;
char s2[20];
strcpy(s2, "; I like you!");
cout << "S2:\t" << s2 << endl;
s1 += s2;
cout << "S1+S2:\t" << s1.GetMyString() << endl;
cout << "S1[4]:\t" << s1[4] << endl;
s1[4] = 'x';
cout<<"now s1[4] is repalced by x"<<endl;
cout << "S1:\t" << s1.GetMyString() << endl;
cout << "S1[999]:\t" << s1[999] << endl;
Mystring s3(" Another myString");
cout << "S3:\t" << s3.GetMyString() << endl;
Mystring s4;
s4 = s1 + s3;
cout << "S1+S3:\t" << s4.GetMyString() << endl;
return 0;
}